我了解 lambda 以及 Func
和 Action
委托。但是表情难住了我。
在什么情况下您会使用 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; }
将有效地编译成一个什么都得不到并返回 10 的 IL 方法。
Expression<Func<int>> myExpression = () => 10;
将被转换为描述一个没有参数并返回值 10 的表达式的数据结构:
https://i.stack.imgur.com/gwU0E.jpg
虽然它们在编译时看起来相同,但编译器生成的完全不同。
我正在添加一个新手答案,因为这些答案似乎在我脑海中,直到我意识到它是多么简单。有时,您期望它很复杂,这使您无法“绕开它”。
直到我遇到一个非常烦人的“错误”,试图通用地使用 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 表达式的内容视而不见。因此,它无法将 lambda 表达式转换为 SQL;但是,它做了次好的事情,并通过我表中的每一行迭代了该条件。
编辑:应约翰彼得的要求解释我的最后一句话:
IQueryable 扩展了 IEnumerable,因此 IEnumerable 的方法(如 Where()
)获得接受 Expression
的重载。当您将 Expression
传递给它时,您会保留一个 IQueryable 作为结果,但是当您传递一个 Func
时,您将退回到基础 IEnumerable,因此您将获得一个 IEnumerable。换句话说,你没有注意到你已经把你的数据集变成了一个要迭代的列表,而不是要查询的东西。除非您真正深入了解签名,否则很难注意到差异。
Compile()
的结果将是 Func<>
,因此您将 Func<>
传递给 Find
方法 - 不涉及 Expression
。所以你的表现会很糟糕。
选择 Expression 与 Func 的一个极其重要的考虑因素是,像 LINQ to Entities 这样的 IQueryable 提供程序可以“消化”您在 Expression 中传递的内容,但会忽略您在 Func 中传递的内容。我有两篇关于这个主题的博客文章:
More on Expression vs Func with Entity Framework 和 Falling in Love with LINQ - Part 7: Expressions and Funcs(最后一部分)
Krzysztof Cwalina 的书(Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries)对此有更哲学的解释;
编辑非图像版本:
大多数情况下,如果只需要运行一些代码,您将需要 Func 或 Action。当代码需要在运行之前进行分析、序列化或优化时,您需要 Expression。表达式用于思考代码,Func/Action 用于运行代码。
database.data.Where(i => i.Id > 0)
作为 SELECT FROM [data] WHERE [id] > 0
执行。如果你只是传入一个 Func,你已经在你的驱动程序上设置了盲点,它所能做的就是 SELECT *
,然后一旦将所有数据加载到内存中,遍历每个并过滤掉所有 id > 0. 将 Func
包装在 Expression
中使驱动程序能够分析 Func
并将其转换为 Sql/MongoDb/other 查询。
Expression
,但当我休假时,它将是 Func/Action
;)
我想就 Func<T>
和 Expression<Func<T>>
之间的区别添加一些注释:
Func
Expression
表达式树可以通过 lambda 表达式语法或 API 语法构建;
表达式树可以编译为委托 Func
反向转换在理论上是可能的,但它是一种反编译,没有内置功能,因为它不是一个简单的过程;
可以通过 ExpressionVisitor 观察/翻译/修改表达式树;
IEnumerable 的扩展方法使用 Func
IQueryable 的扩展方法使用 Expression
有一篇文章描述了代码示例的详细信息:
LINQ: Func<T> vs. Expression<Func<T>>。
希望它会有所帮助。
LINQ 是典型的示例(例如,与数据库交谈),但事实上,任何时候您更关心表达 what 要做什么,而不是实际去做。例如,我在 protobuf-net 的 RPC 堆栈中使用这种方法(以避免代码生成等) - 所以你调用一个方法:
string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));
这将解构表达式树以解析 SomeMethod
(以及每个参数的值),执行 RPC 调用,更新任何 ref
/out
参数,并返回远程调用的结果。这只能通过表达式树实现。我将对此进行更多介绍here。
另一个示例是当您手动构建表达式树以编译为 lambda 时,如 generic operators 代码所做的那样。
当您想将函数视为数据而不是代码时,您将使用表达式。如果您想操作代码(作为数据),您可以这样做。大多数情况下,如果您不需要表达式,那么您可能不需要使用表达式。
主要原因是您不想直接运行代码,而是想检查它。这可能有多种原因:
将代码映射到不同的环境(即 C# 代码到实体框架中的 SQL)
在运行时替换部分代码(动态编程甚至是普通的 DRY 技术)
代码验证(在模拟脚本或进行分析时非常有用)
序列化 - 表达式可以相当容易和安全地序列化,委托不能
对本质上不是强类型的事物进行强类型安全,并利用编译器检查,即使您在运行时进行动态调用(带有 Razor 的 ASP.NET MVC 5 就是一个很好的例子)
Expression
与委托一样不可能序列化,因为任何表达式都可以包含对任意委托/方法引用的调用。当然,“简单”是相对的。
使用 LINQ-to-SQL 时,将 Func<>
传递给 Where()
或 Count()
是不好的。真糟糕。如果您使用 Func<>
,那么它调用 IEnumerable
LINQ 东西而不是 IQueryable
,这意味着整个表被拉入并 然后 被过滤。 Expression<Func<>>
明显更快,因为它在SQL 服务器上执行过滤 - 特别是当您查询位于另一台服务器上的数据库时。
这里过于简化了,但 Func 是一台机器,而 Expression 是一个蓝图。 :D
很高兴知道您可以将 Func<TEntity, bool>
与 AsQueryable()
扩展方法(如 Expression<Func<TEntity, bool>>
)一起使用。
Func<App, bool> filter = x => x.Alias.Contains("gan");
var query = dbSet.Where(filter).AsQueryable();
在您使用 Count()
或 ToList()
等执行方法之前,不会执行查询。
不定期副业成功案例分享
Expression
包含有关某个委托的元信息。Expression<Func<...>>
而不仅仅是Func<...>
时,委托不存在。(isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }
这样的表达式是一个 ExpressionTree,为 If 语句创建分支。