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

Ric*_*gle 910 c# lambda delegates expression-trees

我理解lambdas FuncAction代表们.但表达方式让我很难过.在什么情况下你会使用一个Expression<Func<T>>而不是一个普通的老年人Func<T>

Meh*_*ari 1099

当您想将lambda表达式视为表达式树并查看它们而不是执行它们时.例如,LINQ to SQL获取表达式并将其转换为等效的SQL语句并将其提交给服务器(而不是执行lambda).

从概念上讲,Expression<Func<T>>完全不同Func<T>.Func<T>表示一个delegate几乎是指向方法的指针,Expression<Func<T>>表示lambda表达式的树数据结构.这个树结构描述了lambda表达式的作用而不是实际的东西.它基本上保存有关表达式,变量,方法调用的组合的数据......(例如它保存诸如此lambda之类的信息是一些常量+一些参数).您可以使用此描述将其转换为实际方法(with Expression.Compile)或使用它执行其他操作(如LINQ to SQL示例).将lambdas视为匿名方法和表达式树的行为纯粹是编译时间的事情.

Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }
Run Code Online (Sandbox Code Playgroud)

将有效地编译为一个没有任何东西的IL方法并返回10.

Expression<Func<int>> myExpression = () => 10;
Run Code Online (Sandbox Code Playgroud)

将转换为描述不带参数的表达式并返回值10的数据结构:

表达式与Func 放大图像

虽然它们在编译时看起来都一样,但编译器生成的却完全不同.

  • 换句话说,"表达式"包含关于某个代表的元信息. (89认同)
  • @bertl其实没有.代表根本没参与.与委托有任何关联的原因是您可以将表达式*编译为*委托 - 或者更准确地说,将其编译为方法并将该方法的委托作为返回值.但表达式树本身就是数据.使用`Expression <Func <... >>`而不仅仅是`Func <...>`时,委托不存在. (37认同)
  • @Kyle Delaney`(isAnExample)=> {if(isAnExample)ok(); 否则expandAnswer(); 这样的表达式是一个ExpressionTree,为If语句创建了分支. (5认同)
  • @bertl Delegate是CPU看到的(一个架构的可执行代码),Expression是编译器看到的(只是源代码的另一种格式,但仍然是源代码). (3认同)
  • 你是完全正确的@Luaan.我并不是要在"表达式"和代表之间建立任何人为的关系.我的观点与最初的问题有关,即"表达式<Func <T >>"有什么,"Func <T>"不是什么?我只是想以非常简短的方式总结接受的答案; 不过,听起来有点过于简单. (2认同)
  • @bertl:可能更准确地总结一下,表达式是一个函数字符串构造函数的函数.它不是字符串/ func,但它包含在被要求时创建一个所需的数据. (2认同)

Cha*_*ock 306

我正在添加一个回答因为这些答案似乎超出了我的想法,直到我意识到它是多么简单.有时候你的期望是复杂的,让你无法"绕过它".

直到我走进一个非常烦人的'bug'试图一般地使用LINQ-to-SQL之前,我不需要理解它的区别:

public IEnumerable<T> Get(Func<T, bool> conditionLambda){
  using(var db = new DbContext()){
    return db.Set<T>.Where(conditionLambda);
  }
}
Run Code Online (Sandbox Code Playgroud)

这很有效,直到我开始在更大的数据集上获取OutofMemoryExceptions.在lambda中设置断点让我意识到它在我的表中逐行迭代寻找与我的lambda条件的匹配.这让我感到困惑了一段时间,因为为什么它将我的数据表视为一个巨大的IEnumerable而不是像它应该做的LINQ-to-SQL?它在我的LINQ-to-MongoDb版本中也做了完全相同的事情.

修复只是变成Func<T, bool>Expression<Func<T, bool>>,所以我用谷歌搜索为什么它需要一个Expression而不是在Func这里结束.

表达式只是将委托转换为关于自身的数据.所以a => a + 1变成了"在左侧有一个int a.在右侧你加1." 而已.你现在可以回家了.它显然比那更有条理,但基本上所有的表达树真的是 - 没有什么可以包裹你的头.

理解这一点,很清楚为什么LINQ-to-SQL需要一个Expression,而且Func还不够.Func不带有它进入自身的方式,看看如何将其转换为SQL/MongoDb /其他查询的细节.你无法看到它是否在减法中进行加法或乘法.你所能做的只是运行它.Expression另一方面,允许您查看委托内部并查看它想要执行的所有操作,使您能够将其转换为您想要的任何内容,例如SQL查询.Func没有用,因为我的DbContext对lambda表达式中的实际内容是盲目的把它变成SQL,所以它做了下一个最好的事情,并通过我表中的每一行迭代条件.

编辑:在John Peter的要求下阐述我的最后一句话:

IQueryable扩展了IEnumerable,因此IEnumerable的方法就像Where()获取接受的重载一样Expression.当你传递Expression给它时,你会保留一个IQueryable,但是当你传递一个时Func,你会回到基础IEnumerable上,结果你会得到一个IEnumerable.换句话说,没有注意到您已将数据集转换为要迭代的列表而不是要查询的内容.在您真正了解签名之前,很难注意到差异.

  • 乍得 请进一步说明此评论:“ Func无法正常工作,因为我的DbContext对lambda表达式中的实际内容视而不见,无法将其转换为SQL,因此它做了下一件最好的事情,并遍历了表中的每一行。” (2认同)
  • &gt;&gt; Func... 你所能做的就是运行它。这并不完全正确,但我认为这是应该强调的一点。Funcs/Actions 将被运行,表达式将被分析(在运行之前或什至代替运行)。 (2认同)

LSp*_*777 98

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

更多关于Expression vs Func with Entity Framework爱上LINQ的内容 - 第7部分:表达式和函数(最后一节)


Ole*_*kyi 74

我想补充约之间的差异的一些注意事项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> vs. Expression <Func <T >>.

希望它会有所帮助.


Oğu*_*kan 67

Krzysztof Cwalina的书(框架设计指南:可重用.NET库的约定,惯用法和模式)对此有更多的哲学解释;

Rico Mariani

编辑非图像版本:

大多数情况下,如果需要执行的只是运行一些代码,您将需要FuncAction.在代码需要在运行之前进行分析,序列化或优化时,您需要Expression.表达式用于思考代码,Func/Action用于运行代码.

  • 说得好.即.当您期望将Func转换为某种查询时,您需要表达式.IE浏览器.你需要`database.data.Where(i => i.Id> 0)`作为`SELECT FROM [data] WHERE [id]> 0`执行.如果你只是传入一个Func,你就会在你的驱动程序中加入blinders,它所能做的只是`SELECT*`然后一旦它将所有数据加载到内存中,迭代每个并过滤掉id> 0的所有内容.在`Expression`中包装你的`Func`使驱动程序能够分析`Func`并将其转换成Sql/MongoDb/other查询. (9认同)
  • 因此,当我计划度假时,我会使用“Expression”,但当我度假时,它将使用“Func/Action”;) (2认同)
  • @ChadHedgcock 这是我需要的最后一块。谢谢。我已经研究了一段时间,你在这里的评论让所有的研究都点击了。 (2认同)

Mar*_*ell 37

LINQ是规范的例子(例如,与数据库交谈),但实际上,任何时候你更关心表达要做什么,而不是实际做.例如,我在protobuf-net的RPC堆栈中使用这种方法(以避免代码生成等) - 所以你用以下方法调用一个方法:

string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));
Run Code Online (Sandbox Code Playgroud)

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

另一个例子是当你手动构建表达式树以便编译为lambda时,就像通用运算符代码所做的那样.


And*_*are 19

如果要将函数视为数据而不是代码,则可以使用表达式.如果要操作代码(作为数据),可以执行此操作.大多数情况下,如果您不需要表达式,那么您可能不需要使用表达式.


Lua*_*aan 17

主要原因是您不想直接运行代码,而是想要检查它.这可能有多种原因:

  • 将代码映射到不同的环境(即,实体框架中的C#代码到SQL)
  • 在运行时替换部分代码(动态编程甚至简单的DRY技术)
  • 代码验证(在模拟脚本或进行分析时非常有用)
  • 序列化 - 表达式可以相当容易和安全地序列化,代理人不能
  • 即使你在运行时进行动态调用,也不会强烈地键入强大类型的东西,并利用编译​​器检查(带有Razor的ASP.NET MVC 5就是一个很好的例子)


mhe*_*384 11

我还没有看到任何提及性能的答案.传递Func<>进入Where()或者进入Count()是坏的.真的很糟糕 如果你使用a Func<>然后它调用IEnumerableLINQ东西而不是IQueryable,这意味着整个表被拉入然后被过滤. Expression<Func<>>显着更快,特别是如果您查询的是另一台服务器的数据库.

  • @stt106 当传递给 List&lt;&gt; 的 .Where() 子句时, Expression&lt;Func&lt;&gt;&gt; 会在其上调用 .Compile() ,因此 Func&lt;&gt; 几乎肯定会更快。参见 https://referencesource.microsoft.com/#System.Core/System/Linq/SequenceQuery.cs,6bf428aa1f6b9002,references (3认同)

Xeu*_*ron 6

这里过于简化,但 Func 是一台机器,而 Expression 是一个蓝图。:D