I have a class with a bunch of properties:
class Foo {
public string Name {get; set; }
public int Age {get; set;
}
and a collection of instances of Foo
.
Now I want to order those elements by a property given by the user. So the user selects a property from the type Foo
. Now I want to order by elements based on this property.
One approach is a reflection-based one similar to this:
var p = typeof(Foo).GetProperty("Age");
var ordered = fooList.OrderBy(x => (int) p.GetValue(x, null));
This works so far. However I also tried a second one and there I am stuck. It deals by performing an expression-tree as follows:
var f = GetOrderStatement<Foo>("Age");
var ordered = fooList.OrderBy(f)
With
Func<T, int> GetOrderStatement<T>(string attrName)
{
var type = Expression.Parameter(typeof(T), attrName);
var property = Expression.PropertyOrField(type, attrName);
return Expression.Lambda<Func<T, int>>(property).Compile();
}
My question is: As I should return a Func<T, int>
where to get the int
-part from or in other words where and how do I perform the actual comparison? I suppose I have to make a CallExpression
to IComparable.CompareTo
but I´m not sure how to do so. I think I need access to the both instances to compare.
EDIT: Complete code-example
static void Main()
{
var fooList = new[] { new Foo("Hans", 10), new Foo("Georg", 12), new Foo("Birgit", 40) };
var f = GetOrderStatement<Foo>("Age");
var ordered = fooList.OrderBy(f);
}
private static Func<T, int> GetOrderStatement<T>(string attrName)
{
var type = Expression.Parameter(typeof(T), attrName);
var property = Expression.PropertyOrField(type, attrName);
return Expression.Lambda<Func<T, int>>(property).Compile();
}
Executing this code will throw an
ArgumentException: Incorrect number of parameters supplied for lambda declaration
The problem is that you're trying to build a Func<T, int>
but your call to Expression.Lambda
doesn't specify the parameter expression, which means you can't expect it to create a delegate that has any parameters. Just specifying type
as a second argument to Expression.Lambda
works. Here's a complete example based on your question - note that I've changed the ages to prove that it's actually ordering, and I've updated your fields to read-only properties:
using System;
using System.Linq;
using System.Linq.Expressions;
class Foo
{
public string Name { get; }
public int Age { get; }
public Foo(string name, int age)
{
this.Name = name;
this.Age = age;
}
}
class Test
{
static void Main()
{
var fooList = new[]
{
new Foo("Hans", 12),
new Foo("Georg", 10),
new Foo("Birgit", 40)
};
var f = GetOrderStatement<Foo>("Age");
var ordered = fooList.OrderBy(f);
foreach (var item in ordered)
{
Console.WriteLine($"{item.Name}: {item.Age}");
}
}
private static Func<T, int> GetOrderStatement<T>(string attrName)
{
var type = Expression.Parameter(typeof(T), attrName);
var property = Expression.PropertyOrField(type, attrName);
return Expression.Lambda<Func<T, int>>(property, type).Compile();
}
}
Output:
Georg: 10
Hans: 12
Birgit: 40
See more on this question at Stackoverflow