Answers:
当您想将lambda表达式视为表达式树并在其中查找而不是执行它们时。例如,LINQ to SQL获取表达式并将其转换为等效的SQL语句,然后将其提交给服务器(而不是执行lambda)。
从概念上讲,Expression<Func<T>>
是完全不同的Func<T>
。Func<T>
表示a delegate
,几乎等于方法的指针,Expression<Func<T>>
表示lambda表达式的树数据结构。该树结构描述了lambda表达式的功能,而不是实际的功能。它基本上保存有关表达式,变量,方法调用等信息的数据(例如,它保存诸如lambda是某个常数+一些参数之类的信息)。您可以使用此描述将其转换为实际方法(使用Expression.Compile
),或对其进行其他处理(例如LINQ to SQL示例)。将lambda视为匿名方法和表达式树的行为纯粹是编译时的事情。
Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }
将有效地编译为一无所获并返回10的IL方法。
Expression<Func<int>> myExpression = () => 10;
将转换为描述不带参数且返回值10的表达式的数据结构:
尽管它们在编译时看起来相同,但是编译器生成的内容却完全不同。
Expression
包含有关某个委托的元信息。
Expression<Func<...>>
而不是时,委托不存在Func<...>
。
(isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }
这样的表达式是ExpressionTree,为If语句创建分支。
我添加了一个新手答案,因为这些答案似乎在我头上,直到我意识到它是如此简单。有时,您期望它很复杂,使您无法“绕头绕”。
在走进一个非常烦人的“ bug”尝试通用使用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-to-SQL呢?在我的LINQ-to-MongoDb对应程序中,它也在做完全相同的事情。
解决方法只是变成Func<T, bool>
了Expression<Func<T, bool>>
,所以我用Google搜索了为什么需要一个Expression
而不是Func
,最终到了这里。
表达式只是将委托转换为有关其自身的数据。这样a => a + 1
就变成了“在左侧有一个int a
。在右侧加1”。而已。你现在可以回家了。它显然比这更结构化,但是本质上,这实际上是一个表达式树的全部内容,无所不用其极。
理解了这一点,就清楚了为什么LINQ-to-SQL为什么需要a Expression
和a Func
是不够的。Func
并没有提供一种进入自身的方法,无法了解如何将其转换为SQL / MongoDb /其他查询。您看不到它是在做加法,乘法还是减法。您所能做的就是运行它。Expression
另一方面,允许您查看委托内部,并查看其要执行的所有操作。这使您能够将委托转换为所需的任何内容,例如SQL查询。Func
无法正常工作,因为我的DbContext对lambda表达式的内容视而不见。因此,它无法将lambda表达式转换为SQL。但是,它做的第二件事是最好的,并遍历表中的每一行。
编辑:应约翰·彼得的要求,对我的最后一句话进行解释:
IQueryable扩展了IEnumerable,因此IEnumerable的方法(例如,Where()
获取accept的重载)Expression
。传递Expression
给时,将保留IQueryable的结果,但是传递时Func
,将返回到基本IEnumerable,结果将得到IEnumerable。换句话说,没有注意到您已将数据集变成要迭代的列表,而不是要查询的列表。除非您真正看一下签名,否则很难注意到差异。
在选择Expression vs Func时,一个非常重要的考虑因素是,像LINQ to Entities这样的IQueryable提供程序可以“消化”您在Expression中传递的内容,但是会忽略您在Func中传递的内容。我有两个关于该主题的博客文章:
有关具有实体框架的Expression vs Func以及 对LINQ的热爱的更多信息-第7部分:表达式和Funcs(最后一部分)
我想补充约之间的差异的一些注意事项Func<T>
和Expression<Func<T>>
:
Func<T>
只是普通的老式MulticastDelegate;Expression<Func<T>>
是lambda表达式以表达式树的形式表示;Func<T>
;ExpressionVisitor
;Func<T>
;Expression<Func<T>>
。有一篇文章描述了代码示例的详细信息:
LINQ:Func <T>与Expression <Func <T >>。
希望对您有所帮助。
Krzysztof Cwalina的书(《框架设计指南:可重用的.NET库的约定,惯用语和模式》)对此有一个更哲学的解释。
编辑非图像版本:
大多数情况下,如果只需要运行一些代码,便会需要Func或Action。当需要在运行之前对代码进行分析,序列化或优化时,需要使用Expression。表达式用于考虑代码,Func / Action用于运行代码。
database.data.Where(i => i.Id > 0)
以执行SELECT FROM [data] WHERE [id] > 0
。如果你只是传递一个函数功能,你已经把眼罩上的驱动程序和所有它能做的就是SELECT *
通过每一个和过滤,然后一旦加载所有的数据到内存中,迭代出一切与ID> 0包装一下你Func
在Expression
如虎添翼驱动程序进行分析,Func
并将其转换为Sql / MongoDb / other查询。
Expression
但是当我休假时,我会用Func/Action
;)
LINQ是规范的示例(例如,与数据库对话),但实际上,任何时候您都更在乎表达要做什么而不是实际去做。例如,我在protobuf-net的RPC堆栈中使用此方法(以避免代码生成等)-因此您可以使用以下方法调用方法:
string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));
这将解构表达式树以解析SomeMethod
(以及每个参数的值),执行RPC调用,更新任何ref
/ out
args并从远程调用返回结果。这只能通过表达式树来实现。我在这里详细介绍。
另一个示例是,当您出于通用lambda的编译目的而手动构建表达式树时(由通用运算符代码完成)。
主要原因是当您不想直接运行代码,而是想对其进行检查时。这可能有多种原因:
Expression
像委托一样序列化是不可能的,因为任何表达式都可以包含对任意委托/方法引用的调用。当然,“简单”是相对的。
我还没有任何关于性能的答案。将Func<>
s传入Where()
或传Count()
为错误。真不好 如果使用a,Func<>
则它将调用IEnumerable
LINQ而不是IQueryable
,这意味着将整个表放入并进行过滤。 Expression<Func<>>
速度显着提高,尤其是在查询位于另一台服务器上的数据库时。