Search code examples
c#linqexpressionexpression-trees

Call Enumerable Average via expression


I'm trying to write dynamic code that do some aggregations Average, Sum, Max, etc.

That's the code im executing :

PropertyInfo sortProperty = typeof(T).GetProperty("PropertyName");

ParameterExpression parameter = Expression.Parameter(typeof(T), "p");


MemberExpression propertyAccess = Expression.MakeMemberAccess(parameter, sortProperty);
LambdaExpression orderByExp = Expression.Lambda(propertyAccess, parameter);

var exp = Expression.Lambda<Func<T, int>>(propertyAccess, parameter);

var call = Expression.Call(typeof(Enumerable), "Average", new[] { typeof(IEnumerable<T>) , typeof(Func<T, int>) }, parameter);

and I always get that exception:

No generic method 'Average' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.


Solution

  • Let's look at this line. Here you're calling Call

    var call = Expression.Call(typeof(Enumerable), "Average", new[] { typeof(IEnumerable<T>) , typeof(Func<T, int>) }, parameter);
    

    The third parameter is "An array of Type objects that specify the type parameters of the generic method.". You're passing the types IEnumerable<T> and Func<T, int>, but Average takes only a single type parameter (TSource).

    The forth parameter is "An array of Expression objects that represent the arguments to the method.". You're passing an expression representing a T, but Average expects an IEnumerable<TSource> and a Func<TSource, decimal> (or whatever overload you want to call, I'll just use the decimal one as an example).

    I don't know what your final goal is using this code, but it probably should look like:

    PropertyInfo sortProperty = typeof(T).GetProperty("Prop");
    ParameterExpression parameter = Expression.Parameter(typeof(T), "p");
    MemberExpression propertyAccess = Expression.MakeMemberAccess(parameter, sortProperty);
    
    // parameter for the source collection
    ParameterExpression source = Expression.Parameter(typeof(IEnumerable<T>), "source");
    
    var exp = Expression.Lambda<Func<T, decimal>>(propertyAccess, parameter);
    var call = Expression.Call(typeof(Enumerable), "Average", new[] {typeof(T)}, source, exp);
    

    Here's a small example using this code (you'll get the idea):

    // assuming a class called T with a decimal property called Prop 
    // because I'm a lazy and terrible person
    var method = Expression.Lambda<Func<IEnumerable<T>, decimal>>(call, source).Compile();
    var result = method(new List<T> {new T { Prop=10}, new T { Prop=20}});
    // result is now 15