Updated Multi-Property Sorting with LINQ
In the quest to bring you, the consumer, an ever increasingly useful product, I present the new and improved ListSorter class!
During a peer review, a fellow developer asked if he could use this code to continue sorting on a previously sorted list. The answer was of course, “Erm…” So I took a look at what I had and did some refactoring. The new code (shown below) is now implemented as extension methods to the IEnumerable and IOrderedEnumerable interfaces. The Sort method will perform a primary ordering on an IEnumerable, whereas ContinueSort will perform subsequent ordering on an already sorted collection. Sort will call ContinueSort if there is more than one sort option passed in.
I’m still looking for a good way to supply a collection of IComparer objects to the sorting methods such that these comparers can be applied either to specific properties or to all properties of a given type. I may be able to pass the comparers along as simple objects and cast them to the appropriate type once the property is found, or the type comes up, but that just seems extra messy. I’m interested to hear any ideas on this, and will post an update if a solution is found.
In the meantime, enjoy!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
namespace ESC.SV.UI.Win.RATA.Helpers
{
public static class ListSorter
{
public enum SortingOrder
{
Ascending,
Descending,
};
public static IOrderedEnumerable Sort(this IEnumerable toSort, IDictionary sortOptions)
{
IOrderedEnumerable orderedList = null;
if (sortOptions != null && sortOptions.Count > 0)
{
//Create a copy of our dictionary to preserve the original. We'll be removing items, so we don't want to mess up the reference.
Dictionary sortCopy = sortOptions.Copy();
//Get the primary sort option and remove it from the list
//We will later check if there are additional elements in the list for further sorting
KeyValuePair primarySort = sortCopy.ElementAt(0);
sortCopy.Remove(primarySort.Key);
orderedList = primarySort.Value == SortingOrder.Ascending ? toSort.ApplyOrder(primarySort.Key, "OrderBy") : toSort.ApplyOrder(primarySort.Key, "OrderByDescending");
//Continue the sort if there are more options
if (sortCopy.Count > 0)
{
orderedList = orderedList.ContinueSort(sortCopy);
}
}
return orderedList;
}
public static IOrderedEnumerable ContinueSort(this IOrderedEnumerable orderedList, IDictionary sortOptions)
{
if (sortOptions != null)
{
foreach (KeyValuePair entry in sortOptions)
{
orderedList = entry.Value == SortingOrder.Ascending ? orderedList.ApplyOrder(entry.Key, "ThenBy") : orderedList.ApplyOrder(entry.Key, "ThenByDescending");
}
}
return orderedList;
}
private static IOrderedEnumerable ApplyOrder(this IEnumerable source, string property, string methodName)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
Expression expr = param;
//Create the right-hand part of the lambda expression based on the property provided
foreach (string prop in property.Split('.'))
{
expr = Expression.PropertyOrField(expr, prop);
}
Type delegateType = typeof(Func).MakeGenericType(typeof(T), expr.Type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);
//Fetch the desired method
MethodInfo mi = typeof(Enumerable).GetMethods().Single(method =>
method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2 //
&& method.GetParameters().Length == 2) // source, keySelector
.MakeGenericMethod(typeof(T), expr.Type); // Substitute the appropriate types
return (IOrderedEnumerable)mi.Invoke(null, new object[] { source, lambda.Compile() });
}
private static Dictionary Copy(this IDictionary toCopy)
{
Dictionary dictionaryCopy = new Dictionary();
foreach (KeyValuePair item in toCopy)
{
dictionaryCopy.Add(item.Key, item.Value);
}
return dictionaryCopy;
}
}
}
Explore posts in the same categories: .NET, C#, LINQ
August 1, 2008 at 8:42 pm
Nice Site layout for your blog. I am looking forward to reading more from you.
Tom Humes
January 16, 2010 at 6:24 pm
I think some stuff is missing…like the ‘s and > is showing as >.
January 16, 2010 at 8:19 pm
Thanks, there was a formatting error a while back with WordPress’ code tag, but it looks like that’s all fixed now!