I have a collection of CLR objects. The class definition for the object has three properties: FirstName, LastName, BirthDate.
I have a string that reflects the name of the property the collection should be sorted by. In addition, I have a sorting direction. How do I dynamically apply this sorting information to my collection? Please note that sorting could be multi-layer, so for instance I could sort by LastName, and then by FirstName.
Currently, I'm trying the following without any luck:
var results = myCollection.OrderBy(sortProperty);
However, I'm getting a message that says:
... does not contain a defintion for 'OrderBy' and the best extension method overload ... has some invalid arguments.
Okay, my argument with SLaks in his comments has compelled me to come up with an answer :)
I'm assuming that you only need to support LINQ to Objects. Here's some code which needs significant amounts of validation adding, but does work:
// We want the overload which doesn't take an EqualityComparer.
private static MethodInfo OrderByMethod = typeof(Enumerable)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(method => method.Name == "OrderBy"
&& method.GetParameters().Length == 2)
.Single();
public static IOrderedEnumerable<TSource> OrderByProperty<TSource>(
this IEnumerable<TSource> source,
string propertyName)
{
// TODO: Lots of validation :)
PropertyInfo property = typeof(TSource).GetProperty(propertyName);
MethodInfo getter = property.GetGetMethod();
Type propType = property.PropertyType;
Type funcType = typeof(Func<,>).MakeGenericType(typeof(TSource), propType);
Delegate func = Delegate.CreateDelegate(funcType, getter);
MethodInfo constructedMethod = OrderByMethod.MakeGenericMethod(
typeof(TSource), propType);
return (IOrderedEnumerable<TSource>) constructedMethod.Invoke(null,
new object[] { source, func });
}
Test code:
string[] foo = new string[] { "Jon", "Holly", "Tom", "William", "Robin" };
foreach (string x in foo.OrderByProperty("Length"))
{
Console.WriteLine(x);
}
Output:
Jon
Tom
Holly
Robin
William
It even returns an IOrderedEnumerable<TSource>
so you can chain ThenBy
clauses on as normal :)
You need to build an Expression Tree and pass it to OrderBy
.
It would look something like this:
var param = Expression.Parameter(typeof(MyClass));
var expression = Expression.Lambda<Func<MyClass, PropertyType>>(
Expression.Property(param, sortProperty),
param
);
Alternatively, you can use Dynamic LINQ, which will allow your code to work as-is.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With