关于c#:为什么要使用Expression< Func< T>>

Why would you use Expression<Func<T>> rather than Func<T>?

我了解兰姆达斯和FuncAction代表。但是表情让我很困惑。在什么情况下,您会使用Expression>而不是普通的旧Func


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

从概念上讲,Expression>Func完全不同。Func表示一个delegate相当于一个方法的指针,Expression>表示lambda表达式的树数据结构。此树结构描述lambda表达式所做的,而不是实际执行的操作。它基本上保存了有关表达式、变量、方法调用等组成的数据。(例如,它保存诸如lambda是某个常量+某个参数等信息)。您可以使用此描述将其转换为实际方法(使用Expression.Compile)或使用它执行其他操作(如linq to sql示例)。将lambda视为匿名方法和表达式树的行为纯粹是编译时的事情。

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

将有效地编译为不获取任何内容并返回10的IL方法。

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

将转换为数据结构,该结构描述一个不获取参数并返回值10的表达式:

Expression vs Func大图像

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


我在为noobs添加一个答案,因为这些答案似乎在我的脑海中浮现,直到我意识到这是多么的简单。有时是你的期望,它是复杂的,使你不能'绕着它的头'。

直到我遇到一个非常恼人的"bug",试图使用Linq to SQL,我才需要理解这种区别:

1
2
3
4
5
public IEnumerable<T> Get(Func<T, bool> conditionLambda){
  using(var db = new DbContext()){
    return db.Set<T>.Where(conditionLambda);
  }
}

这非常有效,直到我开始从内存中获得更大数据集的例外。在lambda中设置断点使我意识到它正在逐个遍历表中的每一行,以查找与lambda条件匹配的项。这让我困惑了一段时间,因为它为什么要把我的数据表当作一个巨大的IEnumerable而不是像预期的那样执行LinqToSQL?在我的linq-to-mongodb版本中,它也做了同样的事情。

修复方法只是把Func变成Expression>,所以我在谷歌上搜索为什么它需要Expression而不是Func,最后到了这里。

表达式只是将委托转换为有关其自身的数据。所以a => a + 1变成了"左边有一个int a"。在右边你加了一个。"就这样。你现在可以回家了。它显然比这更具结构性,但本质上所有的表达式树都是——没有什么可以环绕你的头。

了解到这一点,很清楚为什么linq to sql需要一个Expression,而Func不足够。Func没有一种方法可以深入了解自己,了解如何将其转换为SQL/MongoDB/其他查询的细节。你看不出它是在做减法的加法还是乘法。你所能做的就是运行它。另一方面,Expression允许您查看委托内部,查看它想要做的一切,使您能够将其转换为您想要的任何内容,比如SQL查询。Func没有工作,因为我的dbContext对lambda表达式中的实际内容视而不见,无法将其转换为SQL,所以它做了第二件最好的事情,并通过表中的每一行迭代了该条件。

编辑:根据约翰·彼得的要求,对我最后一句话进行阐述:

iQueryable扩展了IEnumerable,因此IEnumerable的方法(如Where()获得接受Expression的重载。当你把一个Expression传递给它时,结果你会保持一个iqueueryable,但是当你把一个Func传递给它时,你会回到ienumerable的基础上,结果你会得到一个ienumerable。换句话说,在没有注意到的情况下,您已经将数据集转换为要迭代的列表,而不是要查询的内容。在你真正看到签名之前,很难发现有什么不同。


在选择表达式vs func时,一个非常重要的考虑因素是,像linq to entities这样的iqueryable提供程序可以"消化"在表达式中传递的内容,但将忽略在func中传递的内容。我有两篇关于这个主题的博客文章:

关于表达式与带有实体框架和爱上Linq第7部分:表达和功能(最后一节)


我想补充一下关于FuncExpression>之间的区别:

  • Func只是一个普通的老派多播代表;
  • Expression>是lambda表达式的表达式树形式表示;
  • 表达式树可以通过lambda表达式语法或API语法构造;
  • 表达式树可以编译为委托Func
  • 逆向转换在理论上是可能的,但它是一种反编译,没有内置的功能,因为它不是一个简单的过程;
  • 表达树可以通过ExpressionVisitor进行观察/翻译/修改;
  • IEnumerable的扩展方法与Func一起使用;
  • iQuery的扩展方法使用Expression>操作。

有篇文章用代码示例描述了详细信息:
linq:funcvs.expression

希望能有所帮助。


从KrzysztofCwalina的书(框架设计指南:可重用.NET库的惯例、习惯用法和模式)中可以得到更多的哲学解释;

Rico Mariani

编辑非图像版本:

Most times you're going to want Func or Action if all that needs to happen is to run some code. You need Expression when the code needs to be analyzed, serialized, or optimized before it is run. Expression is for thinking about code, Func/Action is for running it.


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

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

这将解构表达式树以解析SomeMethod(以及每个参数的值),执行rpc调用,更新任何ref/out参数,并返回远程调用的结果。这只能通过表达式树实现。我在这里再谈一谈。

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


当您希望将函数视为数据而不是代码时,可以使用表达式。如果要操作代码(作为数据),可以这样做。大多数情况下,如果您不需要表达式,那么您可能不需要使用表达式。


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

  • 将代码映射到不同的环境(即实体框架中的C代码到SQL)
  • 在运行时替换部分代码(动态编程,甚至是纯干技术)
  • 代码验证(在模拟脚本或进行分析时非常有用)
  • 序列化-表达式可以很容易和安全地序列化,委托不能
  • 对非固有强类型的事物使用强类型安全性,并利用编译器检查,即使在运行时执行动态调用(使用Razor的ASP.NET MVC 5是一个很好的示例)


我还没有看到任何关于表演的答案。把Func<>s传给Where()Count()是不好的。真坏。如果您使用Func<>,那么它将把IEnumerablelinq的内容称为IQueryable,这意味着整个表被拉入然后过滤。Expression>速度明显更快,尤其是在查询另一台服务器上的数据库时。