提问



我理解lambdas和FuncAction代表。但表达方式让我感到困惑。在什么情况下你会使用Expression<Func<T>>而不是普通的旧Func<T>?

最佳参考


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


放大图像 [48]


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

其它参考1


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


我不需要理解差异,直到我走进一个非常讨厌的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需要一个Expression,而Func不够。Func并没有随身携带它,看看如何将其转换为SQL/MongoDb/其他查询的细节。你不能看它是否在减法上做加法或乘法。你所能做的只是运行它。 Expression,另一方面,允许您查看委托内部并查看它想要做的所有事情,使您能够将其转换为您想要的任何内容,如SQL查询。Func didn因为我的DbContext对lambda表达式中的实际内容将其转换为SQL是盲目的,所以它做了下一个最好的事情,并通过我的表中的每一行迭代条件。


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


IQueryable扩展了IEnumerable,所以IEnumerable的方法如Where()获得接受Expression的重载。当你将Expression传递给它时,你会保留一个IQueryable,但是当你传递一个Func,你重新回到IEnumerable的基础上,你会得到一个IEnumerable。换句话说,没有注意到你已经将数据集转换为要迭代的列表而不是查询的东西。很难注意到差异,直到你真正看到签名的引擎盖。

其它参考2


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


更多关于Expression vs Func with Entity Framework和
爱上LINQ - 第7部分:表达式和功能(最后一部分)[49] [50]

其它参考3


我想补充一些关于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>与表达式< Func< T>>。[51]


希望它会有所帮助。

其它参考4


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



   [52]



编辑非图片版本



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


其它参考5


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


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


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


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

其它参考6


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

其它参考7


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



  • 将代码映射到不同的环境(即C#代码到实体框架中的SQL)

  • 在运行时替换部分代码(动态编程甚至简单DRY技术)

  • 代码验证(在模拟脚本或进行分析时非常有用)

  • 序列化 - 表达式可以相当容易和安全地序列化,代表们可以t

  • 即使您在运行时进行动态调用(使用Razor的ASP.NET MVC 5也是一个很好的例子),对于本质上不具有强类型并且利用编译器检查的事物具有强类型安全性


其它参考8


我没有看到任何提及性能的答案。将Func<>传递给Where()Count()是不好的。真的很糟糕。如果你使用Func<>那么它会调用IEnumerable LINQ的东西而不是IQueryable,这意味着整个表被拉入并且然后被过滤。Expression<Func<>>明显更快,特别是如果你查询数据库住另一台服务器。