1059

我了解 lambdas 和 theFuncAction代表。但是表情难住了我。

在什么情况下你会使用 anExpression<Func<T>>而不是普通的 old Func<T>

4

11 回答 11

1244

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

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

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

将有效地编译成一个什么都得不到并返回 10 的 IL 方法。

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

将被转换为描述一个没有参数并返回值 10 的表达式的数据结构:

表达式与函数 大图

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

于 2009-04-27T13:52:31.423 回答
428

我正在添加一个新手答案,因为这些答案似乎在我脑海中,直到我意识到它是多么简单。有时,您期望它很复杂,这使您无法“绕开它”。

直到我遇到一个非常烦人的“错误”,试图通用地使用 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变成了“在左边有一个int a. 在右边你加 1 ”之类的东西。就是这样。你现在可以回家了。它显然比这更有条理,但这实际上就是一个表达式树的全部内容——没有什么可以让你头疼的。

理解这一点,就很清楚为什么 LINQ-to-SQL 需要 a Expression,而 aFunc是不够的。Func它没有一种进入自身的方式,无法了解如何将其转换为 SQL/MongoDb/其他查询的本质。你看不到它是在做加法还是乘法或减法。你所能做的就是运行它。Expression,另一方面,允许您查看委托内部并查看它想要做的一切。这使您能够将委托转换为您想要的任何内容,例如 SQL 查询。Func不起作用,因为我的 DbContext 对 lambda 表达式的内容视而不见。因此,它无法将 lambda 表达式转换为 SQL;但是,它做了次好的事情,并通过我表中的每一行迭代了该条件。

编辑:应约翰彼得的要求解释我的最后一句话:

IQueryable 扩展了 IEnumerable,因此 IEnumerable 的方法如Where()获取接受的重载Expression。当你将 an 传递Expression给它时,你会保留一个 IQueryable 作为结果,但是当你传递 a 时Func,你会退回到基础 IEnumerable 并且你会得到一个 IEnumerable 作为结果。换句话说,你没有注意到你已经把你的数据集变成了一个要迭代的列表,而不是要查询的东西。除非您真正深入了解签名,否则很难注意到差异。

于 2016-01-05T08:04:54.547 回答
113

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

更多关于使用实体框架的表达式与函数以及 爱上 LINQ - 第 7 部分:表达式和函数(最后一节)

于 2012-01-11T15:57:43.827 回答
100

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

里科·马里亚尼

编辑非图像版本:

大多数情况下,如果只需要运行一些代码,您将需要FuncAction 。当代码需要在运行之前进行分析、序列化或优化时,您需要Expression 。表达式用于思考代码,Func/Action用于运行代码。

于 2016-03-11T13:10:05.150 回答
87

我想添加一些关于 和 之间差异的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>>

希望它会有所帮助。

于 2013-06-11T00:02:19.673 回答
41

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

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

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

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

于 2009-04-27T14:13:25.103 回答
22

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

于 2009-04-27T13:53:05.027 回答
22

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

  • 将代码映射到不同的环境(即 C# 代码到实体框架中的 SQL)
  • 在运行时替换部分代码(动态编程甚至是普通的 DRY 技术)
  • 代码验证(在模拟脚本或进行分析时非常有用)
  • 序列化 - 表达式可以相当容易和安全地序列化,委托不能
  • 对本质上不是强类型的事物进行强类型安全,并利用编译​​器检查,即使您在运行时进行动态调用(带有 Razor 的 ASP.NET MVC 5 就是一个很好的例子)
于 2014-03-26T12:54:33.083 回答
21

使用 LINQ-to-SQL 时,将Func<>s 传递给Where()orCount()是不好的。真糟糕。如果您使用 a Func<>then 它调用IEnumerableLINQ 的东西而不是IQueryable,这意味着整个表被拉入然后过滤。Expression<Func<>>速度要快得多,因为它在 SQL 服务器上执行过滤——尤其是当您查询存在于另一台服务器上的数据库时。

于 2017-06-16T15:58:20.733 回答
9

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

于 2020-08-12T08:51:04.280 回答
2

很高兴知道您可以使用Func<TEntity, bool>扩展AsQueryable()方法,例如Expression<Func<TEntity, bool>>.

Func<App, bool> filter = x => x.Alias.Contains("gan");
var query = dbSet.Where(filter).AsQueryable();

Count()在您使用类似or的执行方法之前,不会执行查询ToList()

于 2021-09-26T18:10:39.793 回答