Rectangle 27 0

c Dynamic chaining of ListT orderby?


public static class Helper
{
    public static IEnumerable<T> BuildOrderBys<T>(
        this IEnumerable<T> source,
        params SortDescription[] properties)
    {
        if (properties == null || properties.Length == 0) return source;

        var typeOfT = typeof (T);

        Type t = typeOfT;

        IOrderedEnumerable<T> result = null;
        var thenBy = false;

        foreach (var item in properties
            .Select(prop => new {PropertyInfo = t.GetProperty(prop.PropertyName), prop.Direction}))
        {
            var oExpr = Expression.Parameter(typeOfT, "o");
            var propertyInfo = item.PropertyInfo;
            var propertyType = propertyInfo.PropertyType;
            var isAscending = item.Direction == ListSortDirection.Ascending;

            if (thenBy)
            {
                var prevExpr = Expression.Parameter(typeof (IOrderedEnumerable<T>), "prevExpr");
                var expr1 = Expression.Lambda<Func<IOrderedEnumerable<T>, IOrderedEnumerable<T>>>(
                    Expression.Call(
                        (isAscending ? thenByMethod : thenByDescendingMethod).MakeGenericMethod(typeOfT, propertyType),
                        prevExpr,
                        Expression.Lambda(
                            typeof (Func<,>).MakeGenericType(typeOfT, propertyType),
                            Expression.MakeMemberAccess(oExpr, propertyInfo),
                            oExpr)
                        ),
                    prevExpr)
                    .Compile();

                result = expr1(result);
            }
            else
            {
                var prevExpr = Expression.Parameter(typeof (IEnumerable<T>), "prevExpr");
                var expr1 = Expression.Lambda<Func<IEnumerable<T>, IOrderedEnumerable<T>>>(
                    Expression.Call(
                        (isAscending ? orderByMethod : orderByDescendingMethod).MakeGenericMethod(typeOfT, propertyType),
                        prevExpr,
                        Expression.Lambda(
                            typeof (Func<,>).MakeGenericType(typeOfT, propertyType),
                            Expression.MakeMemberAccess(oExpr, propertyInfo),
                            oExpr)
                        ),
                    prevExpr)
                    .Compile();

                result = expr1(source);
                thenBy = true;
            }
        }
        return result;
    }

    private static MethodInfo orderByMethod =
        MethodOf(() => Enumerable.OrderBy(default(IEnumerable<object>), default(Func<object, object>)))
            .GetGenericMethodDefinition();

    private static MethodInfo orderByDescendingMethod =
        MethodOf(() => Enumerable.OrderByDescending(default(IEnumerable<object>), default(Func<object, object>)))
            .GetGenericMethodDefinition();

    private static MethodInfo thenByMethod =
        MethodOf(() => Enumerable.ThenBy(default(IOrderedEnumerable<object>), default(Func<object, object>)))
            .GetGenericMethodDefinition();

    private static MethodInfo thenByDescendingMethod =
        MethodOf(() => Enumerable.ThenByDescending(default(IOrderedEnumerable<object>), default(Func<object, object>)))
            .GetGenericMethodDefinition();

    public static MethodInfo MethodOf<T>(Expression<Func<T>> method)
    {
        MethodCallExpression mce = (MethodCallExpression) method.Body;
        MethodInfo mi = mce.Method;
        return mi;
    }
}

public static class Sample
{
    private static void Main()
    {
      var data = new List<Customer>
        {
          new Customer {ID = 3, Name = "a"},
          new Customer {ID = 3, Name = "c"},
          new Customer {ID = 4},
          new Customer {ID = 3, Name = "b"},
          new Customer {ID = 2}
        };

      var result = data.BuildOrderBys(
        new SortDescription("ID", ListSortDirection.Ascending),
        new SortDescription("Name", ListSortDirection.Ascending)
        ).Dump();
    }
}

public class Customer
{
    public int ID { get; set; }
    public string Name { get; set; }
}

I updated the sample as per your request. The properties parameter is now a System.ComponentModel.SortDescription[]. It contains both the property name and an order direction.

In my sample I recursively build an expression and compile per property, but you can construct the entire expression tree with Expression.Block and then only compile once.

The result of the sample as shown in LinqPad

To get the reflection information for the generic method on Enumerable, have a look at the answer on this post: Get a generic method without using GetMethods

Use reflection to get from the string property names to a PropertyInfo's. You can then build an expression tree using the PropertyInfo's to dynamically construct all the orderbys. Once you have the expression tree, compile it to a delegate, (say Func, IEnumerable>) Pass in your _list parameter to this delegate and it will give you the ordered result as another enumerable.

You might also want to expand on this sample to include a bool[] ascending parameter if you want to make some properties sort descending.

Your solution looks very good. Thank you. Just one thing, do you have any ideas on integrating support for ASC and DESC sorting? I added two more MethodInfo, but I can't find how to move forward from that.

Note