为什么在Func和Expression <func>?</func>之间lambda表达式参数不明确

时间:2011-11-26 18:49:32

标签: .net lambda expression-trees

假设我有一个班级:

class MyClass {
    public int MyMethod(Func<int, int> f) { return 0; }
    public int MyMethod(Expression<Func<int, int>> f) { return 1; }
}

当我尝试使用lambda表达式调用该方法时,我收到一个编译错误,指出两次重载之间的调用是不明确的:

var myClass = new MyClass();
myClass.MyMethod(x => 1 + x); // Error!

当然,使用显式类型调用可以正常工作:

myClass.MyMethod((Func<int, int>)(x => 1 + x)); // OK, returns 0
myClass.MyMethod((Expression<Func<int, int>>)(x => 1 + x)); // OK, returns 1

表达式树包含更多信息(实际代码),我可能希望在可用时使用此信息。但我也希望我的代码能够与代表合作。不幸的是,这种含糊不清使得我必须找到另一种方法来区分两个调用,这会混淆一个干净的API。

C#规范没有说明这个具体情况,所以在这个意义上,行为确实符合规范。

但是,有一个论点要求表达式树优先于委托。 Compile方法充当从表达式树到委托的显式转换。表达式树包含更多信息,当您编译为委托时,您将丢失该信息。在另一个方向没有转换。

有没有理由不喜欢表达式树?

3 个答案:

答案 0 :(得分:6)

回答关于标题的问题,它们是模棱两可的,因为类型系统没有&#34; lambda表达式&#34;的概念。它是一个编译器功能,可以转换为委托或表达式树,因此您需要明确要转换它的类型。大多数情况下,编译器会自动推断目标,因为使用lambda表达式的上下文。例如,在IEnumerable扩展方法中使用lambdas而在IQueryable扩展方法中使用lambdas。

现在,为了回答关于为什么不总是喜欢表达式树的问题,你有MagnatLU已经说过的性能参数。如果您接受Expression然后调用Compile以便能够执行它,那么与接受代理相比,它总是会更慢。

两者之间也存在语义差异,委托只是执行某些代码的一种方式,而表达式树是对实际代码的描述。

如果我是你,我会选择将接受表达式的方法的名称更改为明确反映基于委托的额外内容的方法。

答案 1 :(得分:2)

编译器如何知道是选择表达式树还是委托?这是你的决定而不是编译器。

所有linq方法也提供委托和表达式,但它们是目标类型形式的扩展。

 Enumerable.Where<T>(this IEnumerable<T> en , Func<T,bool> filter)
 Queryable.Where<T>(this IQueryable<T> en , Expression<Func<T,bool>> filter)

因此基于目标类型编译器做出选择。编译器是一个程序,实际上它是机器,它的上下文是免费的,它不能像人类一样做出可能更好的决定,它只遵循规则而且这些规则需要明确无误。

答案 2 :(得分:0)

构建和编译表达式树需要时间,并且会给GC带来很大的压力。除非你真的需要,否则你不应该在运行时构造和编译表达式。

编辑另请注意,并非每个表达式或方法都可以表示为Expression<E>Func<U, V>只关心参数'和返回类型,没有这样的限制。