为什么要使用Expression <func <t>&gt;而不是Func <t>?</t> </func <t>

时间:2009-04-27 13:50:15

标签: c# delegates lambda expression-trees

我理解lambdas以及FuncAction代表。但表达方式让我很难过。在什么情况下你会使用Expression<Func<T>>而不是普通的Func<T>

10 个答案:

答案 0 :(得分:1050)

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

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

Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }

将有效地编译为IL方法,该方法不会得到任何内容并返回10.

Expression<Func<int>> myExpression = () => 10;

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

Expression vs Func larger image

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

答案 1 :(得分:281)

我正在添加一个回答因为这些答案似乎在我脑海中,直到我意识到它是多么简单。有时候,你的期望很复杂,使你无法绕过它。

在我走进一个非常烦人的错误之前,我并不需要了解其中的差异。尝试一般使用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>>,因此我搜索了为什么它需要Expression而不是Func,最后来到这里。

表达式只是将代理转换为有关自身的数据。因此a => a + 1变为类似#34;左侧是int a 。在右侧,您可以添加1。&#34; 那就是它。你现在可以回家了。它显然比这更有条理,但基本上所有的表达树确实是 - 没有什么可以包围你的。

理解这一点,很清楚为什么LINQ-to-SQL需要Expression,而Func不够。 Func并没有提供进入自身的方法,看看如何将其转换为SQL / MongoDb /其他查询的细节。你无法看到它是在进行加法还是乘法或减法。你所能做的只是运行它。另一方面,Expression允许您查看委托内部并查看它想要执行的所有操作。这使您能够将委托转换为您想要的任何内容,例如SQL查询。 Func没有用,因为我的DbContext对lambda表达式的内容视而不见。因此,它无法将lambda表达式转换为SQL;然而,它做了下一件最好的事情,并通过我表格中的每一行重复条件。

编辑:在约翰彼得的请求中阐述我的最后一句话:

IQueryable扩展了IEnumerable,因此像Where()这样的IEnumerable方法获得了接受Expression的重载。当你将Expression传递给它时,你会保留一个IQueryable,但是当你传递Func时,你会重新回到基数IEnumerable上并且你会得到一个IEnumerable因此。换句话说,在没有注意到的情况下,您已将数据集转换为要迭代的列表,而不是要查询的内容。很难注意到差异,直到你真正看到签名的内幕。

答案 2 :(得分:96)

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

More on Expression vs Func with Entity FrameworkFalling in Love with LINQ - Part 7: Expressions and Funcs(最后一节)

答案 3 :(得分:70)

我想补充一些关于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>>

希望它会有所帮助。

答案 4 :(得分:61)

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

  

Rico Mariani

编辑非图片版本:

  

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

答案 5 :(得分:36)

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

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

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

另一个例子是当你手动构建表达式树以便编译为lambda时,就像generic operators代码所做的那样。

答案 6 :(得分:19)

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

答案 7 :(得分:16)

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

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

答案 8 :(得分:9)

我没有看到任何提及表现的答案。将Func<>传递到Where()Count()是不好的。真的很糟糕如果您使用Func<>,则会调用IEnumerable LINQ内容而不是IQueryable,这意味着整个表格会被拉入,然后会被过滤掉。 Expression<Func<>>明显更快,特别是如果您要查询另一台服务器的数据库。

答案 9 :(得分:3)

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