Introduction
I expect the reader to be familiar with C# Generics and Reflection.
Some days ago, in the project I am currently working on, I encountered an interesting problem. The problem was to sort a list of objects based on some property.
Let's say we have a Person
class:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime JoiningDate { get; set; }
}
And a list of Person
objects:
Person p1 = new Person { Name = "Kayes", Age = 29, JoiningDate = DateTime.Parse("2010-06-06") };
Person p2 = new Person { Name = "Gibbs", Age = 34, JoiningDate = DateTime.Parse("2008-04-23") };
Person p3 = new Person { Name = "Steyn", Age = 28, JoiningDate = DateTime.Parse("2011-02-17") };
List<Person> persons = new List<Person>();
persons.Add(p1);
persons.Add(p2);
persons.Add(p3);
Suppose we need to sort the persons
list by Age
in ascending order. Now, any C# 3.0 rookie (like me) would do this in the following way:
List<Person> sortedList = persons.OrderBy<Person, int>(p => p.Age).ToList();
The OrderBy()
would sort the list in ascending order by the Age
property and the persons
list would look like this:
So where is the problem? Everything is working fine here! Right. The problem arose when I realized that the property I need to sort the list by is determined at runtime. How so? I had my persons
list projected on an HTML gridview
where each property of the Person
object is shown in each column of the grid. Now, my client wants to click on the column header to sort the grid data by that column, i.e., the corresponding property of the Person
object. Say, the client clicks on the Age
column. So the grid data (the persons) would be sorted by the Age
property.
When the user clicks on the column, I somehow get the name of the property as a string
in the code responsible for sorting the grid data. How the name of the property gets sent to the backend code is beyond the scope of this article. So, we will just assume that the property name is available to us and we just need to sort the list of persons by that property.
Background
When we wrote the OrderBy()
call above to sort the list, we told the method the type of the property to sort the list by, as a generic type parameter and the name of the property using a lambda expression. Therefore the property and its type are determined at compile time. But we cannot afford to do that because there are 3 different properties of 3 different data types in our Person
object. The user may want to sort the grid by any property. So, we somehow have to determine the type and name of the property at runtime. We assumed that we have the name of the property at our disposal. So, identifying the type and using this information to sort the list are what remain to be done.
And along comes Expression Tree to our rescue. Expression Tree is a language feature introduced in C# 3.0. It is used to build lambda expressions dynamically at runtime. More on Expression Trees here.
Let's Cut the Crap and Get Straight Down into the Code
Right. That's what we coders live for. Code. ;)
We need to build the lambda expression that we used above to call the OrderBy()
method. Which is
p => p.Age
p
, here, is the Person
object that is passed in the lambda expression. So, first we will create the ParameterExpression
. A ParameterExpression
denotes the input variable to the left hand side of the lambda (=>
) operator in the lambda expression.
ParameterExpression pe = Expression.Parameter(typeof(Person), "p");
We are giving the name of the parameter exactly as we did when we hard-coded the lambda expression above. Now to build the body of the lambda expression. And here's how we do it:
Expression<Func<Person, int>> expr =
Expression.Lambda<Func<Person, int>>(Expression.Property(pe, "Age"), pe);
Let's analyze the above line. First of all, what is this Func<Person, int>
that is passed as the generic type parameter to the Expression type?
The answer: Func<Person, int>
is a delegate that encapsulates a method that has one parameter (Person
) and returns a value of the type specified by the second parameter (int
). The signature is:
public delegate TResult Func<in T, out TResult>( T arg )
Basically the whole point is the expression we are to build reflects a method denoted by Func<Person, int>
which is what we passed as a hard-wired lambda expression at the beginning of this article. And now we're just building the same with expression tree.
But how do we know what the delegate looks like? That is to say, how can we tell how many input parameters the delegate would take or whether the delegate would return a value at all? Easy. The thing is we are building an expression that we eventually will use to call the OrderBy()
extension method on a IEnumerable<T>
object, i.e, our persons
list. The signature of OrderBy()
is as follows:
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector
)
So that's how we know what the keySelector
delegate will look like. TSource
is the type of the object, the list of which we are to sort. TKey
is the type of the property by which we want to sort the list. Let's resume the rest of the analysis.
Expression<Func<Person, int>> expr =
Expression.Lambda<Func<Person, int>>(Expression.Property(pe, "Age"), pe);
The first parameter that the Expression.Lambda()
static
method takes is the body expression. In our desired lambda expression, the body is just all about returning a property of the Person
object that we have access to through the parameter.
We will create the body expression using the Expression.Property()
static
method which represents accessing a property in an object. We pass the ParameterExpression pe
that we created earlier, as the first parameter to the Expression.Property()
method and the name of the property as the second parameter.
And then again the ParameterExpression pe
as the second parameter to Expression.Lambda()
method which is to tell what our input variable to the lambda expression is.
Here's an important issue to note. In the above code snippet, we are creating the expression with explicit types in the generic type declaration for the delegate. We are hard coding the type of the object (Person
), the list of which we are going to sort. We are also hard coding the type of the property (int
) by which the list would be sorted. The list type might have been known in most cases but we can't afford to hard code the return type of the delegate as we would not know by which type of property the list would be sorted. But later in the article, we are going to wrap our code in a generic method. Therefore we will have the list type and the return type at our disposal through generic type parameters. But for the sake of simplicity for the discussion, let's create the expression with implicit type var
.
var expr = Expression.Lambda(Expression.Property(pe, "Age"), pe);
At this point, our expression is done and if we run a quick watch in Visual Studio on expr
, it would look like this:
Exactly the same as what we have hard-coded at the beginning. Now let's put together all the codes we have written so far:
Person p1 = new Person { Name = "Kayes", Age = 29, JoiningDate = DateTime.Parse("2010-06-06") };
Person p2 = new Person { Name = "Gibbs", Age = 34, JoiningDate = DateTime.Parse("2008-04-23") };
Person p3 = new Person { Name = "Steyn", Age = 28, JoiningDate = DateTime.Parse("2011-02-17") };
List<Person> persons = new List<Person>();
persons.Add(p1);
persons.Add(p2);
persons.Add(p3);
string sortByProp = "Age";
ParameterExpression pe = Expression.Parameter(typeof(Person), "p");
var expr = Expression.Lambda(Expression.Property(pe, sortByProp), pe);
Note that we kept the name of the property in a variable and used that variable in building the expression.
All we have to do now is use the expression in the OrderBy()
method call.
List<Person> sortedList = persons.OrderBy<Person, int>(expr).ToList();
But wait.. what's this?
Seems like there's an error:
'System.Collections.Generic.List<CustomSort.Person>' does not contain a
definition for 'OrderBy' and the best extension method overload
'System.Linq.Queryable.OrderBy<TSource,TKey>(System.Linq.IQueryable<TSource>,
System.Linq.Expressions.Expression<System.Func<TSource,TKey>>)'
has some invalid arguments.
That is because our persons
list is a type of IEnumerable
which does not have an OrderBy()
overload which takes an expression. What do we do now? We just need to convert our list to a IQueryable<T>
IQueryable<Person> query = persons.AsQueryable();
and call OrderBy()
on the IQueryable<Person>
.
List<Person> sortedList = query.OrderBy<Person, int>(expr).ToList();
Therefore, our revised code would look like the following:
Person p1 = new Person { Name = "Kayes", Age = 29, JoiningDate = DateTime.Parse("2010-06-06") };
Person p2 = new Person { Name = "Gibbs", Age = 34, JoiningDate = DateTime.Parse("2008-04-23") };
Person p3 = new Person { Name = "Steyn", Age = 28, JoiningDate = DateTime.Parse("2011-02-17") };
List<Person> persons = new List<Person>();
persons.Add(p1);
persons.Add(p2);
persons.Add(p3);
string sortByProp = "Age";
ParameterExpression pe = Expression.Parameter(typeof(Person), "p");
var expr = Expression.Lambda(Expression.Property(pe, sortByProp), pe);
IQueryable<Person> query = persons.AsQueryable();
List<Person> sortedList = query.OrderBy<Person, int>(expr).ToList();
So we have our expression working with OrderBy()
. But are we done yet? Not quite. Why? Because we are still hard-coding the type of the property that we want to sort our list by, as the generic type parameter to the OrderBy()
method. We need to somehow determine this type at runtime. And the way to do this is by using reflection in the following way:
Type sortByPropType = typeof(Person).GetProperty(sortByProp).PropertyType;
Now we need to call OrderBy()
by passing the type stored in sortByPropType
variable. And here, also, reflection comes into play. First we need to get the corresponding MethodInfo
object for the static
generic OrderBy()
method which is an extension method defined in the static Queryable
class.
MethodInfo orderByMethodInfo = typeof(Queryable)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Single(mi => mi.Name == "OrderBy"
&& mi.IsGenericMethodDefinition
&& mi.GetGenericArguments().Length == 2
&& mi.GetParameters().Length == 2
);
Now that we have our MethodInfo
object for the OrderBy()
method, we need to invoke it in the following way:
List<Person> sortedList = (orderByMethodInfo.MakeGenericMethod(new Type[]
{ typeof(Person), sortByPropType }).Invoke(query, new object[]
{ query, expr }) as IOrderedQueryable<Person>).ToList();
The above reflection codes are definitely not easy on the eyes, I know ;)
But that's it. We have our list sorted by Age
in ascending order in the sortedList
variable.
Final Code
Let's see what our final code looks like:
class Program
{
static void Main(string[] args)
{
Person p1 = new Person { Name = "Kayes", Age = 29, JoiningDate = DateTime.Parse("2010-06-06") };
Person p2 = new Person { Name = "Gibbs", Age = 34, JoiningDate = DateTime.Parse("2008-04-23") };
Person p3 = new Person { Name = "Steyn", Age = 28, JoiningDate = DateTime.Parse("2011-02-17") };
List<Person> persons = new List<Person>();
persons.Add(p1);
persons.Add(p2);
persons.Add(p3);
string sortByProp = "Age";
Type sortByPropType = typeof(Person).GetProperty(sortByProp).PropertyType;
ParameterExpression pe = Expression.Parameter(typeof(Person), "p");
var expr = Expression.Lambda(Expression.Property(pe, sortByProp), pe);
IQueryable<Person> query = persons.AsQueryable();
MethodInfo orderByMethodInfo =
typeof(Queryable).GetMethods(BindingFlags.Public | BindingFlags.Static)
.Single(mi => mi.Name == "OrderBy"
&& mi.IsGenericMethodDefinition
&& mi.GetGenericArguments().Length == 2
&& mi.GetParameters().Length == 2
);
List<Person> sortedList =
(orderByMethodInfo.MakeGenericMethod(new Type[]
{ typeof(Person), sortByPropType }).Invoke(query,
new object[] { query, expr }) as
IOrderedQueryable<Person>).ToList();
}
}
Improvements
While our code above works just fine, it is heavily dependent on the Person
object. So if need be, for lists of other types of objects we have to write basically the same code with different objects other than Person
. So I was thinking of making the code reusable and generic so that it can accommodate any type of object we provide. We can do it by creating an extension method for the IEnumerable
type.
public static class MyExtensions
{
public static List<T> CustomSort<T, TPropertyType>
(this IEnumerable<T> collection, string propertyName, string sortOrder)
{
List<T> sortedlist = null;
IQueryable<T> query = collection.AsQueryable<T>();
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
Expression<Func<T, TPropertyType>> expr = Expression.Lambda<Func<T, TPropertyType>>(Expression.Property(pe, propertyName), pe);
if (!string.IsNullOrEmpty(sortOrder) && sortOrder == "desc")
sortedlist = query.OrderByDescending<T, TPropertyType>(expr).ToList();
else
sortedlist = query.OrderBy<T, TPropertyType>(expr).ToList();
return sortedlist;
}
}
And invoke the extension method using reflection in the following way:
List<Person> sortedList = typeof(MyExtensions).GetMethod("CustomSort").
MakeGenericMethod(new Type[] { typeof(Person), sortByPropType }).
Invoke(persons, new object[] { persons, sortByProp, "asc" }) as List<Person>;
And if necessary, we can still call the extension method with hard-coded types as easily as the following way:
sortedList = persons.CustomSort<Person, int>(sortByProp, "desc");
Edit (08-04-2011)
Thanks to Alois Kraus who suggested that there is no need to convert the IEnumerable<T>
to IQueryable<T>
since we can call the Compile()
method on Expression<TDelegate>
object which compiles the lambda expression described by the expression tree into executable code and produces a delegate that represents the lambda expression. We can then pass that delegate to IEnumerable<T>.OrderBy()
method. So our revised extension method should be:
public static class MyExtensions
{
public static List<T> CustomSort<T, TPropertyType>
(this IEnumerable<T> collection, string propertyName, string sortOrder)
{
List<T> sortedlist = null;
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
Expression<Func<T, TPropertyType>> expr = Expression.Lambda<Func<T, TPropertyType>>(Expression.Property(pe, propertyName), pe);
if (!string.IsNullOrEmpty(sortOrder) && sortOrder == "desc")
sortedlist = collection.OrderByDescending<T, TPropertyType>(expr.Compile()).ToList();
else
sortedlist = collection.OrderBy<T, TPropertyType>(expr.Compile()).ToList();
return sortedlist;
}
}
Conclusion
Expression Tree is the coolest language feature C# 3.0 introduced. And along with Lambda Expression, which is also a C# 3.0 feature, Expression Trees can open lots of doors to interesting possibilities. I am definitely looking forward to using these two more and more in my future projects.