Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

A Generic Run-time LINQ-based Multi-level Object Sorter

4.43/5 (4 votes)
25 Oct 2009CPOL2 min read 15.9K  
A Generic helper function that dynamically sorts objects by properties of the object that aren't known at compile-time

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:

C#
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:

  1. An IEnumerable<T> of objects to sort
  2. 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)
  3. A Func<T, SK, string> to extract the value from T based on information in SK to actually sort on
  4. 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.

C#
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)