Click here to Skip to main content
15,881,803 members
Articles / Programming Languages / C#

Sort IEnumerableT by column name

Rate me:
Please Sign up or sign in to vote.
4.33/5 (2 votes)
14 Apr 2009CPOL3 min read 42.8K   204   15   12
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:

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

What my UI master wanted was:

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

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

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

Points of interest

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

C#
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:

C#
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.

C#
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:

C#
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:

C#
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.

C#
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.

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

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

C#
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 :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)


Written By
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 Pin
Magnus_12-May-10 2:38
Magnus_12-May-10 2:38 
GeneralRe: Dynamic Linq Pin
Troy W Phoenix12-May-10 3:25
Troy W Phoenix12-May-10 3:25 
GeneralAvoiding reflection Pin
Richard Deeming15-Apr-09 9:09
mveRichard Deeming15-Apr-09 9:09 
GeneralRe: Avoiding reflection Pin
Troy W Phoenix17-Apr-09 10:09
Troy W Phoenix17-Apr-09 10:09 
GeneralRe: Avoiding reflection Pin
Sacha Barber13-Jan-10 6:05
Sacha Barber13-Jan-10 6:05 
Very nice

Sacha Barber
  • Microsoft Visual C# MVP 2008/2009
  • Codeproject MVP 2008/2009
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralRe: Avoiding reflection Pin
TweeZz30-Jun-16 2:58
TweeZz30-Jun-16 2:58 
GeneralRe: Avoiding reflection Pin
Richard Deeming30-Jun-16 3:01
mveRichard Deeming30-Jun-16 3:01 
QuestionThe code? Pin
User 304249810-Apr-09 5:37
User 304249810-Apr-09 5:37 
AnswerRe: The code? Pin
Troy W Phoenix12-Apr-09 15:52
Troy W Phoenix12-Apr-09 15:52 
AnswerRe: The code? Pin
Troy W Phoenix12-Apr-09 17:01
Troy W Phoenix12-Apr-09 17:01 
GeneralRe: The code? Pin
Troy W Phoenix13-Apr-09 3:27
Troy W Phoenix13-Apr-09 3:27 

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

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