Introduction
I recently came upon a situation where I had a list of objects each of which had properties. These objects were pulled from Active Directory, with various properties of the AD objects loaded in a StringDictionary
. I wanted to be able to sort these objects based on configuration, without hardcoding any particular property or order to sort on, and I wanted to be able to sort on multiple properties, so for example, LastName
Ascending, then FirstName
Descending.
Of course, you can't simply iterate through the list of properties that you want to sort on and sort, because each sort would wipe out the previous sort.
LINQ does provide methods to handle this scenario, OrderBy
and ThenBy
. But without knowing the properties that you want to sort by at compile time, it's just a tiny bit trickier. In this article, I demonstrate a simple generic function that helps to sort lists of data on an arbitrary number of sort keys, with each level of sort supporting ascending and descending order.
The Function
Here is the helper function:
private IEnumerable<T> MultiLevelSort<T, SK>(
IEnumerable<T> list,
List<SK> sortKeys,
Func<T, SK, string> keySelector,
Func<SK, bool> ascendingSelector)
{
if (sortKeys.Count == 0) return list;
IOrderedEnumerable<T> res = null;
for (int i = 0; i < sortKeys.Count; i++)
{
SK sk = sortKeys[i];
bool ascending = ascendingSelector(sk);
if (i == 0)
{
if (ascending) res = list.OrderBy(r => keySelector(r, sk));
else res = list.OrderByDescending(r => keySelector(r, sk));
}
else
{
if (ascending) res = res.ThenBy(r => keySelector(r, sk));
else res = res.ThenByDescending(r => keySelector(r, sk));
}
}
return res;
}
This code takes in four parameters:
- An
IEnumerable<T>
of objects to sort - A
List<SK>
of objects that contain sorting order information (note that this list itself is expected to already be in the correct sort order) - A
Func<T, SK, string>
to extract the value from T
based on information in SK to actually sort on - A
Func<SK, bool>
to extract the ascending/descending information from SK - true
means ascending, false
is descending
… and it returns the list correctly sorted as an IEnumerable<T>
. Note that the actual object returned is an IOrderedEnumerable<T>
as long as there is at least one valid sort key.
Using the Code
Here is an example of how to use the helper function. It assumes that you have a list of 'MyProperty
' in an object called 'AllProperties
', of which some specify sorting information.
List<MyProperty> sortProps = AllProperties
.Where(sp => sp.Sort != string.Empty)
.OrderBy(sp => sp.SortOrder).ToList();
IEnumerable<MyObject> sortedResults = MultiLevelSort<MyObject, MyProperty>(
results, sortProps,
(r, sp) => r.Properties.ContainsKey(sp.Name) ? r.Properties[sp.Name] : string.Empty,
sp => sp.Sort == "Ascending"
);
... where (in this example) MyObject
is an object that contains a StringDictionary
called ‘Properties
’, and MyProperty
is an object that contains properties called ‘Sort
’ (“Ascending/Descending”), ‘SortOrder
’ (an integer), and ‘Name
’ (the name of the property within the MyObject.Properties
collection that we want to sort on).
Your objects can obviously have their own structures or properties that provide the data to sort on - it doesn't have to be StringDictionary
as in this example.
History
- v1.0 - 2009 Oct 26 - Initial publication