通过表达式调用 Enumerable 平均值函数

4
我将尝试编写动态代码来执行一些聚合操作,比如平均值、总和、最大值等等。
以下是我正在执行的代码:
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);

我总是遇到这个异常:

在类型“System.Linq.Enumerable”上没有通用方法“Average”与提供的类型参数和参数兼容。如果该方法是非泛型的,则不应提供类型参数。


该异常意味着你正在尝试使用错误的参数调用Average方法。请确保你正在使用正确的参数类型,并且你正在使用泛型版本的Average方法。

T 是从哪里来的?它是一个通用参数,因此在运行时是一个具体类型吗?我怀疑你必须寻找未经过类型化的 Average 方法(类似于 typeof(IEnumerable<>), typeof(Func<, int>)),然后调用 MakeGenericMethod 来创建针对 T 类型的版本。 - O. R. Mapper
另外,当调用一个有两个参数的方法时,你不应该传递两个参数给 Call 吗? - O. R. Mapper
1个回答

3

让我们看一下这行代码。在这里,你正在调用Call

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

第三个参数是"Type对象的数组,指定泛型方法的类型参数。"。您正在传递类型IEnumerable<T>Func<T,int>,但Average只需要单个类型参数(TSource)

第四个参数是"表示方法参数的Expression对象的数组。"。您正在传递代表T的表达式,但Average需要一个IEnumerable<TSource>和一个Func<TSource,decimal>(或者您想要调用的其他重载,我将仅以decimal为例)。

我不知道您使用此代码的最终目标是什么,但它可能应该看起来像:

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);

这里有一个使用此代码的小例子(你会明白的):
// 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

非常感谢您的快速回复,但我仍然在以下行中遇到异常抛出:var exp = Expression.Lambda<Func<T, double>>(propertyAccess, parameter);System.ArgumentException附加信息:类型为'System.Int32'的表达式不能用于返回类型为'System.Double'的表达式。我已经将该行修改为:Expression.Lambda<Func<T, double>>(propertyAccess, parameter);而不是Expression.Lambda<Func<T, decimal>>(propertyAccess, parameter); - user2711610
我认为你应该使用var exp = Expression.Lambda<Func<Transaction, int>>(propertyAccess, parameter);Average有很多重载,很容易搞错类型。 - sloth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接