Click here to Skip to main content
Click here to Skip to main content

Sort IEnumerableT by column name

, 14 Apr 2009
Rate this:
Please Sign up or sign in to vote.
Extension method overload of OrderBy which uses column name instead of lambda.

Introduction

An in depth discussion of Expression Trees and Extension methods is beyond the scope of this article. There are plenty of sources available to learn about that stuff. You probably should go check them out first. Now, on with it...

There is obviously more than one way to skin a cat. This is no exception I'm sure, but this was a pretty fun way. This is an implementation of an extension method overload of IEnumerable<T>.OrderBy().

Background

I can't, for the life of me, remember why but one day, my UI developer came to me and said something along the lines of, "I need to be able to run OrderBy on this List, but I can't use a lambda to the column name because I don't know it until runtime, and all I will have is a string of the name". I immediately thought of overloading OrderBy() to take a string representing the name of the attribute of the items in the list to order by as opposed to the existing method that takes a lambda. As I began thinking about how to implement it though, the idea started forming to take the string name and somehow generate the lambda which I could then use to defer to the original OrderBy(). It seemed like something that should be possible, and it sounded much more interesting than just writing sort code. How often does a developer get a break from the same old <insert typical backoffice system functionality here>? I hope you enjoy it. I sure enjoyed writing it.

Using the code

Everyone using LINQ is probably familiar with lambdas and most likely using them, or have used them in OrderBy() by now. You might see, for example, sorting a list of products by their price done like this:

var sortedProducts = myProductsList.OrderBy(p => p.Price);

What my UI master wanted was:

var sortedProducts = myProductsList.OrderBy("Price");

And, that's exactly what I gave him. Obviously, I implemented an OrderByDescending() as well.

var sortedProducts = myProductsList.OrderByDescending("Price");

Points of interest

This is the method signature for the extension method for OrderBy().

public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector)

As with any extension method, the first parameter represents the object (this) that the method will be called on. It is then of no particular interest to us here. The second parameter is where the lambda signifying which column to sort by goes. That is the guy we want to supplant with our string containing the column name. So, we want:

"Price"

instead of:

p => p.Price

The first two lines of interest are:

ParameterExpression list = Expression.Parameter(typeof(T), "list");
MemberExpression property = Expression.Property(list, columnName);

The first creates a ParameterExpression (PE) named "list", for lack of something more interesting to call it, based on the type T of which we have an IEnumerable of. The second creates a MemberExpression (ME) on our PE to set up access to the "Price" property (in our example; yours will be whatever you want to sort on). Next, we have a bit of Reflection.

MethodInfo expressionMaker = me.GetMethod("MakeExpression", 
           BindingFlags.Static | BindingFlags.NonPublic);
MethodInfo expressionMethod = 
           expressionMaker.MakeGenericMethod(typeof(T), property.Type);

There's a method included in the source called MakeExpression. We reflect into the containing class in the first call, and get a MethodInfo pointing to it. In the second call, since MakeExpression is a generic method, we have to resolve the generic. Next, we actually call our configured method:

var lambda = expressionMethod.Invoke(null, new object[] { property, list });

At this point, we actually have a lambda which looks like what OrderBy() actually expects; namely:

list => list.Price

We turn this into runnable code by compiling it like so:

var func = (lambda as LambdaExpression).Compile();

Now, a little more Reflection to get a handle on the particular Orderby() that we want to call. I used a little LINQ here for some flair. I'm not convinced this was the best/safest way to do it.

var sortMaker = 
      (from s in enumerable.GetMethods()
       where
          (s.Name == ((IsAscending) ? ASCENDING : DESCENDING))
          && (s.GetParameters().Length == 2) //need a more reliable way to do this
      select s).First();

As before, our method "handle" is for a generic method, so we first need to tell it what the generic will be resolved into before we call it.

MethodInfo sorter = sortMaker.MakeGenericMethod(typeof(T), property.Type);

And now, we defer to it using our concocted lambda:

 return sorter.Invoke(source, new object[] { source, func }) 
               as IOrderedEnumerable<T>;

Don't forget to reference the namespace where you put this class (using foo;) so the extension method will show up.

P.S. Microsoft has a library that does this called Dynamic API, which I promise I did not know about until 6 months after doing this Big Grin | :-D

History

Thanks to Joe for calling me out and forcing me to beef up the explanation.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Troy Beacleay
Software Developer (Senior)
United States United States
Been designing, implementing, and maintianing projects in .Net since the beginning of time...or so it seems. I've done a little of everything before that though; java, vb classic, c, c++, assembly, pascal, basic.
 
*I'm not a big fan of UI work.
 
_

Comments and Discussions

 
GeneralDynamic Linq PinmemberPartenon12-May-10 2:38 
GeneralRe: Dynamic Linq PinmemberTroy Beacleay12-May-10 3:25 
GeneralAvoiding reflection PinmemberRichard Deeming15-Apr-09 9:09 
GeneralRe: Avoiding reflection PinmemberTroy Beacleay17-Apr-09 10:09 
GeneralRe: Avoiding reflection PinmvpSacha Barber13-Jan-10 6:05 
QuestionThe code? PinmemberJoe Enos10-Apr-09 5:37 
AnswerRe: The code? PinmemberTroy Beacleay12-Apr-09 15:52 
AnswerRe: The code? PinmemberTroy Beacleay12-Apr-09 17:01 
Ok Joe. You convinced me. I hope the new info helps Smile | :) Cheers
 
If you do that, the compiler won't warn you, but if you turn up the volume you might hear it laugh.

GeneralRe: The code? PinmemberJoe Enos12-Apr-09 18:40 
GeneralRe: The code? PinmemberTroy Beacleay13-Apr-09 3:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 14 Apr 2009
Article Copyright 2009 by Troy Beacleay
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid