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

我了解lambda以及FuncAction代表。 但是expression式使我感到遗憾。 你会在什么情况下使用Expression<Func<T>>而不是普通的旧的Func<T>

当你想把lambdaexpression式当作expression式树来看,而不是执行它们。 例如,LINQ to SQL获取expression式并将其转换为等效的SQL语句,并将其提交给服务器(而不是执行lambda)。

从概念上讲, Expression<Func<T>>Func<T> 完全不同Func<T>表示delegate ,它几乎是指向方法的指针,而Expression<Func<T>>表示lambdaexpression式的树数据结构 。 这个树结构描述了lambdaexpression式的作用,而不是做实际的事情。 它基本上保存有关expression式,variables,方法调用,…的组成的数据(例如,它保存诸如这个lambda的信息是某个常量+某个参数)。 你可以用这个描述把它转换成一个实际的方法(用Expression.Compile )或者用其他的东西(比如LINQ to SQL的例子)。 把lambda作为匿名方法和expression式树对待纯粹是编译时间的事情。

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

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

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

将被转换为一个数据结构,该数据结构描述了一个不带参数的expression式并返回值10:

表达与功能 更大的图像

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

我添加了一个答案为noobs,因为这些答案似乎在我的头上,直到我意识到这是多么简单。 有时候,你的期望是复杂的,使你无法“围绕它”。

直到我走进一个非常烦人的“错误”,试图一般使用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到SQL像它应该? 在我的LINQ到MongoDb版本中,它也做了同样的事情。

解决的办法就是把Func<T, bool>变成Expression<Func<T, bool>> ,所以我去Google为什么需要一个Expression而不是Func ,最后在这里结束。

expression式只是简单地将一个委托变成关于它自己的数据。 所以a => a + 1变成了“在左边有一个int a ,在右边你给它加1”。 而已。 你现在可以回家了 这显然比这个更结构化,但这基本上都是一个expression树 – 没有任何东西可以包装你的头。

了解到这一点,为什么LINQ-to-SQL需要一个Expression ,而Func是不够的。 Func并没有带入自己的方式,看到如何将它翻译成SQL / MongoDb /其他查询的细节。 你不能看到是否加减乘法。 你所能做的就是运行它。 另一方面, Expression允许您查看委托内部并查看它想要执行的任何操作,从而使您能够将其转换为任何所需的内容,如SQL查询。 Func没有工作,因为我的DbContext对于lambdaexpression式中实际上是什么把它变成SQL是盲目的,所以它做了下一个最好的事情,并通过我的表中的每一行迭代条件。

编辑:在约翰·彼得的要求上阐述我的最后一句话:

IQueryable扩展了IEnumerable,因此IEnumerable的方法如Where()获取接受Expression重载。 当你传递一个Expression ,你保留了一个IQueryable作为结果,但是当你传递一个Func ,你将回落到基本的IEnumerable上,并且你会得到一个IEnumerable结果。 换句话说,没有注意到你已经把你的数据集变成了一个被迭代的列表,而不是被查询的东西。 直到你真正看到签名的底层,很难注意到有什么不同。

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

更多关于expression式与Func与entity framework和LINQ爱上 – 第7部分:expression式和function (最后一节)

我想添加一些有关Func<T>Expression<Func<T>>之间区别的注释:

  • Func<T>只是一个普通的老派MulticastDelegate;
  • Expression<Func<T>>是以expression式树的forms表示lambdaexpression式;
  • expression式树可以通过lambdaexpression式语法或通过API语法来构造;
  • expression式树可以编译成一个委托Func<T> ;
  • 反向转换在理论上是可能的,但这是一种反编译,没有内置的function,因为它不是一个简单的过程;
  • expression式树可以通过ExpressionVisitor观察/翻译/修改;
  • IEnumerable的扩展方法使用Func<T> ;
  • IQueryable的扩展方法使用Expression<Func<T>>

有一篇文章描述了代码示例的细节:
LINQ:Func <T>与expression式<Func <T >> 。

希望这会有帮助。

LINQ是一个典型的例子(例如,与数据库交谈),但事实上,任何时候你更关心的是expression该做什么,而不是真的这样做。 例如,我在protobuf-net的RPC栈中使用这种方法(以避免代码生成等) – 所以你调用一个方法:

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

这将解构expression式树来parsingSomeMethod (以及每个参数的值),执行RPC调用,更新任何ref / out参数,并返回远程调用的结果。 这只能通过expression式树来实现。 我在这里更多地介绍

另一个例子是当你为了编译到lambda而手动构buildexpression式树时,就像通用操作符代码一样。

Krzysztof Cwalina的书( Framework Design Guidelines:Conventions,Idioms,and Patterns for Reusable .NET Libraries )对此有更多的哲学解释。

里科马里亚尼

编辑非图像版本:

大多数时候你会想要Func或者Action,如果所有需要的都是运行一些代码的话。 代码在运行之前需要进行分析,序列化或优化时需要expression式 expression式是考虑代码, Func / Action是为了运行它。

当你想把你的函数当作数据而不是代码的时候,你会使用一个expression式。 你可以做这个,如果你想操纵代码(作为数据)。 大多数时候,如果你没有看到expression式的需要,那么你可能不需要使用一个。

主要原因是当你不想直接运行代码,而是想要检查它。 这可能是由于许多原因:

  • 将代码映射到不同的环境(即entity framework中的C#代码到SQL)
  • 在运行时replace部分代码(dynamic编程甚至简单的DRY技术)
  • 代码validation(在模拟脚本或进行分析时非常有用)
  • 序列化 – expression式可以很容易和安全地序列化,委托人不能
  • 强types的安全性不是固有的强types,即使在运行时进行dynamic调用也可以利用编译器检查(带有Razor的ASP.NET MVC 5就是一个很好的例子)

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