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

Dynamic Sorting in LINQ, Part 2

4.00/5 (2 votes)
11 Oct 2013CPOL2 min read 14.7K  
Dynamically sort query results using LINQ expressions and reflection with sorting

Introduction

Continuing from my previous tip, "Dynamic Sorting in LINQ", we now add sorting into the mix. This is done by making use of delegates to give us a succinct, efficient manner to decide on which extension method should be called, either OrderBy or OrderByDescending.

Using the Code

In this example, we're going to begin by defining a basic data model as a structure containing the first name, last name, and middle initial of a person:

C#
public struct Person 
{
    public string FirstName { get; set; }
    public string MiddleInitial { get; set; }
    public string LastName { get; set; }

    public override string ToString() {
        return String.Format("{0}, {1} {2}", LastName, FirstName, MiddleInitial);
    }
}   

Next, we set up a data store containing a list of people:

C#
public class PeopleDataStore {
    public const bool SortAscending = true;
    public const bool SortDescending = false;

    private List<Person> m_people = null;

    public PeopleDataStore() 
    {
        m_people = new List<Person>();

        m_people.AddRange(new Person[] {
            new Person() { FirstName = "John", MiddleInitial = "Q", LastName = "Doe" },
            new Person() { FirstName = "Jane", MiddleInitial = null, LastName = "Appleton" },
            new Person() { FirstName = "Jim", MiddleInitial = "H", LastName = "Smith" },
            new Person() { FirstName = "Joe", MiddleInitial = null, LastName = "Plumber" }
        });
    }

    public List<Person> People 
    {
        get { return m_people; }
    }

    public List<Person> GetPeople(string sortPropertyName, bool sortAscending) 
    {
        if (!String.IsNullOrEmpty(sortPropertyName)) {
            Type personType = typeof(Person);
            
            if (personType.GetProperties().Any(prop => prop.Name == sortPropertyName)) {
                PropertyInfo pinfo = personType.GetProperty(sortPropertyName);
                Func<Person, object> orderByExpr = (person => pinfo.GetValue(person, null));

                Func<IEnumerable<Person>, IOrderedEnumerable<Person>> sortFunc = null;
                if (sortAscending) sortFunc = (list => list.OrderBy(orderByExpr));
                else sortFunc = (list => list.OrderByDescending(orderByExpr));

                return sortFunc(m_people).ToList();
            }
        }

        return m_people;
    }
} 

Let's take a quick jaunt through the code. First, we define a couple of Boolean constants for better legibility when calling the GetPeople method; either these constants or literal Boolean values can be used for the sortAscending parameter on the method. Next, we create a strongly-typed list of the Person type to hold our data values, then populate the list in the constructor; providing a corollary read-only property of the same type for access to the list.

The GetPeople method is then defined with two parameters: the first being the name of the public property from the Person structure used for sorting, and then a Boolean flag indicating whether the list should be sorted in ascending or descending order.

If there is no value supplied for the sortPropertyName parameter, the method simply returns the list values in their default order. If a value is supplied, we then check to see if the Person type defines a property of the same name and continue processing should this prove true. From there, we obtain a PropertyInfo instance which will be used by a Func delegate to be passed into an extension method for ordering. The Func delegate, orderByExpr, is a lambda expression which matches the delegate signature for both the OrderBy and OrderByDescending extension methods.

Next, we create another Func delegate to define which sorting method to call based on the sortAscending parameter supplied to the method call. Since we know that the OrderBy and OrderByDescending methods are extensions supplied to the IEnumerable(T) interface and our list implements this interface, we define the Func delegate to accept an input parameter of List<Person>. The output parameter is then defined as the same return type as defined by the OrderBy and OrderByDescending methods, this being an instance of IOrderedEnumerable(T). With the delegate defined, we then return the result.

Now, to put the code to use in a simple console app:

C#
public static class Program 
{
    static void Main() 
    {
        PeopleDataStore dataStore = new PeopleDataStore();
        Action<Person> consoleAction = (person => Console.WriteLine(person));

        Console.WriteLine("All people listed in default order:");
        dataStore.People.ForEach(consoleAction);

        Console.WriteLine();
        Console.WriteLine("All people listed in reverse alphabetical order by last name:");
        dataStore.GetPeople("LastName", PeopleDataStore.SortDescending)
            .ForEach(consoleAction);
    }
} 

License

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