协慌网

登录 贡献 社区

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

我理解 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 的数据结构:

表达式与Func 放大图像

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

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

直到我走进一个非常烦人的'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没有工作,因为我的 DbContext 对 lambda 表达式中的实际内容无法将其转换为 SQL,所以它做了下一个最好的事情,并通过表中的每一行迭代条件。

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

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

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

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