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:
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:
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:
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);
}
}