Sunday, April 17, 2011

Lambda Wrangling For Fun : Part 4 – Sorting Multiple Columns

 

We can approach the problem of sorting a dataset by multiple properties using the same techniques as for the filter case.

A single-property sorting lambda looks like:

….OrderBy(_ => _.property) or ….OrderByDescending(_ => _.property)

If we wanted to sort by more than one property, we would do something like:

….OrderBy(_ => _.property1).OrderByDescending(_ => _.property2)

It’s important to notice two points:



  • The lambdas for each sort clause are self-contained – unlike the composite filter lambda where the same parameter is threaded through each predicate.
  • The direction of the sort is specified by the use of either the OrderBy or OrderByDescending extension method. This means that the composite sort criteria will be represented by a chain of OrderBy and/or OrderByDescending methods.

Bearing these points in mind, we can develop a general Sort action specification:

public interface ISort<TSource>
{
string PropertyName { get; }
SortDirection SortDirection { get; }

Expression<Func<TSource, object>> GetSelectorLambda();
}

The properties are as we expected, and we have encountered a variant GetSelectorLambda before.


Applying a composite sort on an enumerable can be achieved by:

public static IOrderedEnumerable<T> Sort<T>(this IEnumerable<T> _this, IEnumerable<ISort<T>> sortOperations)
{
var parameterExpression = Expression.Parameter(typeof (IEnumerable<T>), "_");

Expression items = parameterExpression;
foreach (var sortOperation in sortOperations)
{
// selector : __ => __.{sortOperation.PropertyName}
var selector = sortOperation.GetSelectorLambda();

// items.OrderBy<T, object>(__ => __.Property)
// is actually
// Enumerable.OrderBy<T, object>(items, __ => __.Property)
items = Expression.Call(typeof (Enumerable),
sortOperation.SortDirection == SortDirection.Ascending ? "OrderBy" : "OrderByDescending",
new [ ] { typeof (T), typeof (object) },
items,
selector);
}

// _ => _.OrderBy[Descending](__ => __.Property1)...OrderBy[Descending](__ => __.PropertyN)
var lambda = Expression.Lambda<Func<IEnumerable<T>, IOrderedEnumerable<T>>>(items, parameterExpression);

// compile the lambda...
var func = lambda.Compile();

// ...call the chain of extension methods on (_this)
return func(_this);
}

The function generates calls to either the ‘OrderyBy’ or the ‘OrderByDescending’ method on the Enumerable type, using the previous result as the first argument, thus generating the chain.


Figure1It finally compiles and executes the chain of methods on the actual enumerable instance passed in.


Here is a typical use:

var sorts = new List<ISort<IPerson>> {
new Sort<IPerson>("Timestamp", SortDirection.Descending),
new Sort<IPerson>("Name", SortDirection.Ascending),
};

var sortedPersons = persons.Sort(sorts);