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)
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 :D
History
Thanks to Joe for calling me out and forcing me to beef up the explanation.