I came across a scenario where I need to sort a list of custom type on different properties based on input. With the help of few articles, I was able to come up with generic implementation using LINQ.During unit testing, one of the test failed because implicit conversion was happening when lamda expression was created using Expression tree.
Below I have put the sample code to understand the issue (Not sure why formatting was not getting correct, sorry for that)
static class ExtensionMethods
{
public static IEnumerable<TSource> Sort<TSource>(this IEnumerable<TSource> unSortedList, Func<TSource, object> selector, bool isAscending)
{
return isAscending ? unSortedList.OrderBy(selector) : unSortedList.OrderByDescending(selector);
}
}
class Program
{
class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
static void Main(string[] args)
{
var unOrderedStudents = new List<Student>
{
new Student{ Name="A", Age=20},
new Student{Name = "B", Age=19}
};
//This Works
var sortUsingLamda = unOrderedStudents.Sort<Student>(stud => stud.Age, true);
//Exception - Expression of type 'System.Int32' cannot be used for return type 'System.Object'
var sortUsingExpressionTree = unOrderedStudents.Sort<Student>( GetSortFunc<Student>("Age"), true);
Console.WriteLine("Press any key to continue");
Console.ReadLine();
}
private static Func<T, object> GetSortFunc<T>(string sortColumn)
{
var param = Expression.Parameter(typeof(T), "entity");
var propertyExpression = Expression.Property(param, sortColumn);
var boxingExpression = Expression.Convert(propertyExpression, typeof(object));
return Expression.Lambda<Func<T, object>>(propertyExpression, param).Compile();
//after adding Convert expression issue got fixed
//return Expression.Lambda<Func<T, object>>(boxingExpression, param).Compile();
}
}
In the Main method, when I try to pass a Func delegate directly to Sort extension method it works but it fails with Expression tree.
I found a similar issue, but that talks about constrained type parameters. Is both the issues same? Can somebody help me understand the issue.
You need to use the boxed version (you currently create boxingExpression
, but base your final query instead on propertyExpression
):
return Expression.Lambda<Func<T, object>>(boxingExpression, param).Compile();
Re why this isn't implicit - there simply is no implicit casting here; Expression
!= C#. Boxing is a non-trivial operation, and the Expression
API requires a specific node in the tree.
You've prototypes GetSortFunc as returning a Func<> instance which returns an object. Because of this it's your job to ensure that the expression tree you generate yields an object.
Although int in implicitly convert to an object in C# under the hood it's being boxed. That's why you need the boxing expression in your code and why you need to generate the lambda using the expression you get back from Expression.Convert. The best way to think about it is that when using expression trees you have to be explicit about all conversions and not think of it in terms of how you'd write the C# code.
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