为什么 Func 和表达式之间的 lambda 表达式参数模棱两可<Func>?



假设我有一个类:

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方法充当了从表达式树到委托的显式转换。表达式树包含更多信息,当编译为委托时,将丢失这些信息。没有其他方向的转换

有什么理由不喜欢表达式树吗?

回答标题上的问题,它们是不明确的,因为类型系统没有"lambda表达式"的概念。它是一个编译器特性,可以被转换成委托或表达式树,所以你需要显式地转换成你想要转换的类型。大多数情况下,由于使用lambda表达式的上下文,编译器还会自动推断目标。例如,IEnumerable扩展方法中lambdas的使用与IQueryable扩展方法中lambdas的使用。

现在,要回答为什么不总是更喜欢表达式树的问题,您可以使用MagnatLU已经说明的性能参数。如果你接受一个Expression,然后调用Compile来执行它,那么与接受委托相比,它总是会慢一些。

两者在语义上也有区别,委托只是执行一些代码的一种方式,而表达式树是对实际代码的描述。

如果我是你,我会选择将接受表达式的方法的名称更改为能够清楚地反映它对基于委托的方法所做的额外工作的名称。

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

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

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

编译器根据目标类型做出选择。编译器是一个程序,实际上它是机器,它与上下文无关,它不能像人类一样决定什么可能更好,它只遵循规则,这些规则需要明确。

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

Edit:还要记住,不是每个表达式或方法都可以表示为Expression<E>Func<U, V>只关心参数和返回类型,没有这样的限制。

相关内容

最新更新