Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically Sorting with LINQ

Tags:

linq

I have a collection of CLR objects. The class definition for the object has three properties: FirstName, LastName, BirthDate.

I have a string that reflects the name of the property the collection should be sorted by. In addition, I have a sorting direction. How do I dynamically apply this sorting information to my collection? Please note that sorting could be multi-layer, so for instance I could sort by LastName, and then by FirstName.

Currently, I'm trying the following without any luck:

var results = myCollection.OrderBy(sortProperty);

However, I'm getting a message that says:

... does not contain a defintion for 'OrderBy' and the best extension method overload ... has some invalid arguments.

like image 672
user564042 Avatar asked Jan 18 '11 15:01

user564042


2 Answers

Okay, my argument with SLaks in his comments has compelled me to come up with an answer :)

I'm assuming that you only need to support LINQ to Objects. Here's some code which needs significant amounts of validation adding, but does work:

// We want the overload which doesn't take an EqualityComparer.
private static MethodInfo OrderByMethod = typeof(Enumerable)
    .GetMethods(BindingFlags.Public | BindingFlags.Static)
    .Where(method => method.Name == "OrderBy" 
           && method.GetParameters().Length == 2)
    .Single();

public static IOrderedEnumerable<TSource> OrderByProperty<TSource>(
    this IEnumerable<TSource> source,
    string propertyName) 
{
    // TODO: Lots of validation :)
    PropertyInfo property = typeof(TSource).GetProperty(propertyName);
    MethodInfo getter = property.GetGetMethod();
    Type propType = property.PropertyType;
    Type funcType = typeof(Func<,>).MakeGenericType(typeof(TSource), propType);
    Delegate func = Delegate.CreateDelegate(funcType, getter);
    MethodInfo constructedMethod = OrderByMethod.MakeGenericMethod(
        typeof(TSource), propType);
    return (IOrderedEnumerable<TSource>) constructedMethod.Invoke(null,
        new object[] { source, func });
}

Test code:

string[] foo = new string[] { "Jon", "Holly", "Tom", "William", "Robin" };

foreach (string x in foo.OrderByProperty("Length"))
{
    Console.WriteLine(x);
}

Output:

Jon
Tom
Holly
Robin
William

It even returns an IOrderedEnumerable<TSource> so you can chain ThenBy clauses on as normal :)

like image 124
Jon Skeet Avatar answered Jan 04 '23 19:01

Jon Skeet


You need to build an Expression Tree and pass it to OrderBy.
It would look something like this:

var param = Expression.Parameter(typeof(MyClass));
var expression = Expression.Lambda<Func<MyClass, PropertyType>>(
    Expression.Property(param, sortProperty),
    param
);

Alternatively, you can use Dynamic LINQ, which will allow your code to work as-is.

like image 23
SLaks Avatar answered Jan 04 '23 21:01

SLaks