Lambda expressions provide a simple method by which you can sort an IEnumerable object by just providing the property on which to sort via a string parameter.
Here is the code:
public static List<T> Sort<T>(IEnumerable<T> list, string sortField, SortDirection sortDirection)
{
var param = Expression.Parameter(typeof(T), string.Empty);
//normally one would use Expression.Property(param, sortField), but that doesnt work
//when working with interfaces where the sortField is defined on a base interface.
//so instead we search for the Property through our own GetProperty method and use it to build the
//Expression property
PropertyInfo propertyInfo = GetProperty(typeof (T), sortField);
var property = Expression.Property(param, propertyInfo);
var lambda = Expression.Lambda<Func<T, object>>(Expression.Convert(property, typeof(object)), param);
var returnList;
if (sortDirection == SortDirection.Ascending)
returnList = list.AsQueryable().OrderBy(lambda).ToList();
else
returnList = list.AsQueryable().OrderByDescending(lambda).ToList();
return returnList;
}
///
// Allows you to get the PropertyInfo for a property defined on the provided type
// Code allows you to find the property even from base interfaces (Type.GetProperty does not return
// properties from base interfaces
// if you wish to look for a specific property defined on a certain interface, then provide the
// propertyName as 'interface.propertyname'
///
private static PropertyInfo GetProperty(Type type, string propertyName)
{
string typeName = string.Empty;
if (propertyName.Contains("."))
{
//name was specified with typename - so pull out the typename
typeName = propertyName.Substring(0, propertyName.IndexOf("."));
propertyName = propertyName.Substring(propertyName.IndexOf(".")+1);
}
PropertyInfo prop = type.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);
if (prop == null)
{
var baseTypesAndInterfaces = new List<Type>();
if (type.BaseType != null) baseTypesAndInterfaces.Add(type.BaseType);
baseTypesAndInterfaces.AddRange(type.GetInterfaces());
foreach (Type t in baseTypesAndInterfaces)
{
prop = GetProperty(t, propertyName);
if (prop != null)
{
if (!string.IsNullOrEmpty(typeName) && t.Name != typeName)
continue; //keep looking as the typename was not found
break;
}
}
}
return prop;
}
Some notes about the code above:
- To create an property expression using the Expression.Property method worked fine when I was using classes. But the minute I started using interfaces where the property was defined on a base interface, Expression.Property began to fail.
- The reason for the failure was that .Net by default does not return properties defined on base interfaces via the call Type.GetProperty. Hence Expression.Property fails too.
- Instead, I implemented a custom method that would walk up the entire hierarchy of the interface/class and attempt to find a property with the provided name. The PropertyInfo returned by the method is then used to create the Expression property.
To call the above method:
var sortedList = Sort<MyClassOrInterface>(aListContainingObjectsOfTypeMyClassOrInterface, “APropertyDefinedOnMyInterfaceOrClass”, SortDirection.Ascending);
The above method works well when you need to sort based on a property that is defined by a string. (Works great when you have to work with Asp.Net grids where the sorting field is defined by name).
But if you instead need a strongly typed method of sorting you can use the following code:
public List<T> Sort<T,T1>(IEnumerable<T> list,
Func<T, T1> sorter, SortDirection direction)
{
List<T> returnList = null;
if (direction == SortDirection.Ascending)
returnList = list.OrderBy(sorter).ToList();
else
returnList = list.OrderByDescending(sorter).ToList();
return returnList;
}
And to call the above method use the following code: (Where C is the name of a class/interface and Name is a property defined on that class/interface and cList is a list).
List<C> sorted = Sort<C,string>(cList, c => c.Name, SortDirection.Ascending);
No comments:
Post a Comment
Remember, if you want me to respond to your comment, then you need to use a Google/OpenID account to leave the comment.