为什么要使用Expression <Func <T >>而不是Func <T>?


948

我了解lambda和FuncAction代表。但是表情让我难过。

在什么情况下,您会使用Expression<Func<T>>而不是普通的旧版本Func<T>


14
Func <>将转换为c#编译器级别的方法,Expression <Func <>>将在直接编译代码后在MSIL级别执行,这就是它更快的原因
Waleed AK

1
除了答案之外,csharp语言规范“ 4.6表达式树类型”还有助于交叉引用
djeikyb

Answers:


1133

当您想将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的表达式的数据结构:

表达与功能 放大图像

尽管它们在编译时看起来相同,但是编译器生成的内容却完全不同


95
因此,换句话说,an Expression包含有关某个委托的元信息。
bertl

39
@bertl实际上,不。代表完全不参与。究其原因有一个在所有委托任何关联的是,你可以编译的表达,以委托-或者更准确地说,它编译成一个方法,并获得代表该方法的返回值。但是表达式树本身就是数据。当您使用Expression<Func<...>>而不是时,委托不存在Func<...>
罗安2015年

5
@Kyle Delaney (isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }这样的表达式是ExpressionTree,为If语句创建分支。
Matteo Marciano-MSCP

3
@bertl Delegate是CPU看到的(一种体系结构的可执行代码),Expression是编译器看到的(仅仅是另一种格式的源代码,但仍然是源代码)。
Codewarrior

5
@bertl:可以说一个表达式对一个函子来说就像一个字符串生成器对一个字符串一样,可以更准确地总结一下。它不是字符串/函数,但包含被要求创建一个数据所需的数据。
扁平的

336

我添加了一个新手答案,因为这些答案似乎在我头上,直到我意识到它是如此简单。有时,您期望它很复杂,使您无法“绕头绕”。

在走进一个非常烦人的“ 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。换句话说,没有注意到您已将数据集变成要迭代的列表,而不是要查询的列表。除非您真正看一下签名,否则很难注意到差异。


2
乍得 请进一步说明此评论:“ Func无法正常工作,因为我的DbContext对lambda表达式中的实际内容视而不见,无法将其转换为SQL,因此它做了下一件最好的事情,并遍历了表中的每一行。”
约翰·彼得斯

2
>> Func ...您所能做的就是运行它。这不是完全正确,但是我认为这是应该强调的重点。要运行Funcs / Action,要分析表达式(在运行之前,甚至代替运行)。
康斯坦丁

@Chad这里的问题是吗?:db.Set <T>查询了所有数据库表,然后,因为.Where(conditionLambda)使用了Where(IEnumerable)扩展方法,该方法在内存中的整个表中枚举。我认为您会收到OutOfMemoryException,因为此代码试图将整个表加载到内存中(当然也创建了对象)。我对吗?谢谢:)
BenceVégert18年

104

在选择Expression vs Func时,一个非常重要的考虑因素是,像LINQ to Entities这样的IQueryable提供程序可以“消化”您在Expression中传递的内容,但是会忽略您在Func中传递的内容。我有两个关于该主题的博客文章:

有关具有实体框架的Expression vs Func以及 对LINQ的热爱的更多信息-第7部分:表达式和Funcs(最后一部分)


+ l进行解释。但是我得到“ LINQ to Entities不支持LINQ表达式节点类型'Invoke'。” 并且在获取结果后必须使用ForEach。
tymtam

77

我想补充约之间的差异的一些注意事项Func<T>Expression<Func<T>>

  • Func<T> 只是普通的老式MulticastDelegate;
  • Expression<Func<T>> 是lambda表达式以表达式树的形式表示;
  • 表达式树可以通过lambda表达式语法或API语法构造;
  • 表达式树可以编译为委托Func<T>;
  • 从理论上讲,逆转换是可能的,但是这是一种反编译,因为它不是一个简单的过程,所以没有内置功能;
  • 表达树可以通过观察/翻译/修饰ExpressionVisitor;
  • IEnumerable的扩展方法与Func<T>;
  • IQueryable的扩展方法使用Expression<Func<T>>

有一篇文章描述了代码示例的详细信息:
LINQ:Func <T>与Expression <Func <T >>

希望对您有所帮助。


不错的清单,您需要指出的一点是,可以进行逆转换,但不能进行精确的逆转换。在转换过程中一些元数据会丢失。但是,您可以将其反编译为在再次编译时产生相同结果的表达式树。
Aidiakapi 2015年

76

Krzysztof Cwalina的书(《框架设计指南:可重用的.NET库的约定,惯用语和模式》)对此有一个更哲学的解释。

里科·马里亚尼(Rico Mariani)

编辑非图像版本:

大多数情况下,如果只需要运行一些代码,便会需要FuncAction。当需要在运行之前对代码进行分析,序列化或优化时,需要使用Expression表达式用于考虑代码,Func / Action用于运行代码。


10
说得好。即。当您希望将Func转换为某种查询时,需要表达。就是 您需要database.data.Where(i => i.Id > 0)以执行SELECT FROM [data] WHERE [id] > 0。如果你只是传递一个函数功能,你已经把眼罩上的驱动程序和所有它能做的就是SELECT *通过每一个和过滤,然后一旦加载所有的数据到内存中,迭代出一切与ID> 0包装一下你FuncExpression如虎添翼驱动程序进行分析,Func并将其转换为Sql / MongoDb / other查询。
乍得Hedgcock '16

因此,当我计划休假时,我会用,Expression但是当我休假时,我会用Func/Action;)
GoldBishop

1
@ChadHedgcock这是我需要的最后一块。谢谢。我已经看了一段时间了,您在这里的评论使所有研究都点击了。
约翰尼

37

LINQ是规范的示例(例如,与数据库对话),但实际上,任何时候您都更在乎表达要做什么而不是实际去做。例如,我在protobuf-net的RPC堆栈中使用此方法(以避免代码生成等)-因此您可以使用以下方法调用方法:

string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));

这将解构表达式树以解析SomeMethod(以及每个参数的值),执行RPC调用,更新任何ref/ outargs并从远程调用返回结果。这只能通过表达式树来实现。我在这里详细介绍

另一个示例是,当您出于通用lambda的编译目的而手动构建表达式树时(由通用运算符代码完成)。


20

当您要将函数视为数据而不是代码时,可以使用表达式。如果要操纵代码(作为数据),则可以执行此操作。大多数时候,如果您不需要表达式,则可能不需要使用表达式。


19

主要原因是当您不想直接运行代码,而是想对其进行检查时。这可能有多种原因:

  • 将代码映射到其他环境(即,将C#代码映射到Entity Framework中的SQL)
  • 在运行时替换部分代码(动态编程甚至普通的DRY技术)
  • 代码验证(在仿真脚本或进行分析时非常有用)
  • 序列化-表达式可以很容易且安全地进行序列化,委托不能
  • 在本质上不是强类型的事物上具有强类型的安全性,即使您在运行时中进行动态调用,也可以利用编译器检查(带有Razor的ASP.NET MVC 5是一个很好的示例)

您能详细说明一下5号

@ uowzd01只是看一下Razor,它广泛使用了这种方法。
a安2015年

@Luaan我正在寻找表达式序列化,但是没有有限的第三方使用就找不到任何东西。.Net 4.5是否支持表达式树序列化?
vabii

@vabii我不知道-在一般情况下,这并不是一个好主意。我的意思是,您可以针对要支持的特定情况编写非常简单的序列化,并且可以针对预先设计的接口进行编码-我已经做了几次。一般情况下,Expression像委托一样序列化是不可能的,因为任何表达式都可以包含对任意委托/方法引用的调用。当然,“简单”是相对的。
a安

15

我还没有任何关于性能的答案。将Func<>s传入Where()或传Count()为错误。真不好 如果使用a,Func<>则它将调用IEnumerableLINQ而不是IQueryable,这意味着将整个表放入并进行过滤。 Expression<Func<>>速度显着提高,尤其是在查询位于另一台服务器上的数据库时。


这是否也适用于内存中查询?
stt106

@ stt106可能不是。
mhenry1384 '18

仅当您枚举列表时,这才是正确的。如果使用GetEnumerator或foreach,则不会将ienumerable完全加载到内存中。
nelsontruran

1
@ stt106当传递到List <>的.Where()子句时,Expression <Func <>>会调用.Compile(),因此Func <>几乎可以肯定更快。见referencesource.microsoft.com/#System.Core/System/Linq/...
NStuke
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.