为什么要使用Expression <Func <T >>而不是Func <T>?
我了解lambda以及Func
和Action
代表。 但是expression式使我感到遗憾。 你会在什么情况下使用Expression<Func<T>>
而不是普通的旧的Func<T>
?
当你想把lambdaexpression式当作expression式树来看,而不是执行它们。 例如,LINQ to SQL获取expression式并将其转换为等效的SQL语句,并将其提交给服务器(而不是执行lambda)。
从概念上讲, Expression<Func<T>>
与Func<T>
完全不同 。 Func<T>
表示delegate
,它几乎是指向方法的指针,而Expression<Func<T>>
表示lambdaexpression式的树数据结构 。 这个树结构描述了lambdaexpression式的作用,而不是做实际的事情。 它基本上保存有关expression式,variables,方法调用,…的组成的数据(例如,它保存诸如这个lambda的信息是某个常量+某个参数)。 你可以用这个描述把它转换成一个实际的方法(用Expression.Compile
)或者用其他的东西(比如LINQ to SQL的例子)。 把lambda作为匿名方法和expression式树对待纯粹是编译时间的事情。
Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }
将有效地编译成一个没有任何东西并返回10的IL方法。
Expression<Func<int>> myExpression = () => 10;
将被转换为一个数据结构,该数据结构描述了一个不带参数的expression式并返回值10:
更大的图像
虽然在编译时它们看起来是一样的,但是编译器产生的却完全不同 。
我添加了一个答案为noobs,因为这些答案似乎在我的头上,直到我意识到这是多么简单。 有时候,你的期望是复杂的,使你无法“围绕它”。
直到我走进一个非常烦人的“错误”,试图一般使用LINQ-to-SQL时,我不需要了解其中的差异:
public IEnumerable<T> Get(Func<T, bool> conditionLambda){ using(var db = new DbContext()){ return db.Set<T>.Where(conditionLambda); } }
这工作很好,直到我开始在较大的数据集上OutofMemoryExceptions。 在lambda里面设置断点让我意识到,它是遍历我的表中的每一行一个接一个寻找匹配我的lambda条件。 这难倒了我一段时间,因为为什么它是对待我的数据表作为一个巨大的IEnumerable而不是做LINQ到SQL像它应该? 在我的LINQ到MongoDb版本中,它也做了同样的事情。
解决的办法就是把Func<T, bool>
变成Expression<Func<T, bool>>
,所以我去Google为什么需要一个Expression
而不是Func
,最后在这里结束。
expression式只是简单地将一个委托变成关于它自己的数据。 所以a => a + 1
变成了“在左边有一个int a
,在右边你给它加1”。 而已。 你现在可以回家了 这显然比这个更结构化,但这基本上都是一个expression树 – 没有任何东西可以包装你的头。
了解到这一点,为什么LINQ-to-SQL需要一个Expression
,而Func
是不够的。 Func
并没有带入自己的方式,看到如何将它翻译成SQL / MongoDb /其他查询的细节。 你不能看到是否加减乘法。 你所能做的就是运行它。 另一方面, Expression
允许您查看委托内部并查看它想要执行的任何操作,从而使您能够将其转换为任何所需的内容,如SQL查询。 Func
没有工作,因为我的DbContext对于lambdaexpression式中实际上是什么把它变成SQL是盲目的,所以它做了下一个最好的事情,并通过我的表中的每一行迭代条件。
编辑:在约翰·彼得的要求上阐述我的最后一句话:
IQueryable扩展了IEnumerable,因此IEnumerable的方法如Where()
获取接受Expression
重载。 当你传递一个Expression
,你保留了一个IQueryable作为结果,但是当你传递一个Func
,你将回落到基本的IEnumerable上,并且你会得到一个IEnumerable结果。 换句话说,没有注意到你已经把你的数据集变成了一个被迭代的列表,而不是被查询的东西。 直到你真正看到签名的底层,很难注意到有什么不同。
Expression vs Func的select非常重要,因为像LINQ to Entities这样的IQueryable提供者可以“消化”你在Expression中传递的内容,但是会忽略你在Func中传递的内容。 我有两个关于这个主题的博客文章:
更多关于expression式与Func与entity framework和LINQ爱上 – 第7部分:expression式和function (最后一节)
我想添加一些有关Func<T>
和Expression<Func<T>>
之间区别的注释:
-
Func<T>
只是一个普通的老派MulticastDelegate; -
Expression<Func<T>>
是以expression式树的forms表示lambdaexpression式; - expression式树可以通过lambdaexpression式语法或通过API语法来构造;
- expression式树可以编译成一个委托
Func<T>
; - 反向转换在理论上是可能的,但这是一种反编译,没有内置的function,因为它不是一个简单的过程;
- expression式树可以通过
ExpressionVisitor
观察/翻译/修改; - IEnumerable的扩展方法使用
Func<T>
; - IQueryable的扩展方法使用
Expression<Func<T>>
。
有一篇文章描述了代码示例的细节:
LINQ:Func <T>与expression式<Func <T >> 。
希望这会有帮助。
LINQ是一个典型的例子(例如,与数据库交谈),但事实上,任何时候你更关心的是expression该做什么,而不是真的这样做。 例如,我在protobuf-net的RPC栈中使用这种方法(以避免代码生成等) – 所以你调用一个方法:
string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));
这将解构expression式树来parsingSomeMethod
(以及每个参数的值),执行RPC调用,更新任何ref
/ out
参数,并返回远程调用的结果。 这只能通过expression式树来实现。 我在这里更多地介绍
另一个例子是当你为了编译到lambda而手动构buildexpression式树时,就像通用操作符代码一样。
Krzysztof Cwalina的书( Framework Design Guidelines:Conventions,Idioms,and Patterns for Reusable .NET Libraries )对此有更多的哲学解释。
编辑非图像版本:
大多数时候你会想要Func或者Action,如果所有需要的都是运行一些代码的话。 代码在运行之前需要进行分析,序列化或优化时需要expression式 。 expression式是考虑代码, Func / Action是为了运行它。
当你想把你的函数当作数据而不是代码的时候,你会使用一个expression式。 你可以做这个,如果你想操纵代码(作为数据)。 大多数时候,如果你没有看到expression式的需要,那么你可能不需要使用一个。
主要原因是当你不想直接运行代码,而是想要检查它。 这可能是由于许多原因:
- 将代码映射到不同的环境(即entity framework中的C#代码到SQL)
- 在运行时replace部分代码(dynamic编程甚至简单的DRY技术)
- 代码validation(在模拟脚本或进行分析时非常有用)
- 序列化 – expression式可以很容易和安全地序列化,委托人不能
- 强types的安全性不是固有的强types,即使在运行时进行dynamic调用也可以利用编译器检查(带有Razor的ASP.NET MVC 5就是一个很好的例子)
我没有看到任何提到性能的答案。 将Func<>
传递到Where()
或Count()
是不好的。 真不好。 如果你使用Func<>
那么它调用IEnumerable
LINQ的东西,而不是IQueryable
,这意味着整个表被拉入, 然后过滤。 Expression<Func<>>
明显更快,特别是如果您正在查询另一台服务器的数据库。