我了解 lambdas 和 theFunc
和Action
代表。但是表情难住了我。
在什么情况下你会使用 anExpression<Func<T>>
而不是普通的 old Func<T>
?
我了解 lambdas 和 theFunc
和Action
代表。但是表情难住了我。
在什么情况下你会使用 anExpression<Func<T>>
而不是普通的 old Func<T>
?
当您想将 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 的表达式的数据结构:
虽然它们在编译时看起来相同,但编译器生成的却完全不同。
我正在添加一个新手答案,因为这些答案似乎在我脑海中,直到我意识到它是多么简单。有时,您期望它很复杂,这使您无法“绕开它”。
直到我遇到一个非常烦人的“错误”,试图通用地使用 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 作为结果。换句话说,你没有注意到你已经把你的数据集变成了一个要迭代的列表,而不是要查询的东西。除非您真正深入了解签名,否则很难注意到差异。
选择 Expression 与 Func 的一个极其重要的考虑因素是,像 LINQ to Entities 这样的 IQueryable 提供程序可以“消化”您在 Expression 中传递的内容,但会忽略您在 Func 中传递的内容。我有两篇关于这个主题的博客文章:
更多关于使用实体框架的表达式与函数以及 爱上 LINQ - 第 7 部分:表达式和函数(最后一节)
我想添加一些关于 和 之间差异的Func<T>
注释Expression<Func<T>>
:
Func<T>
只是一个普通的老式 MulticastDelegate;Expression<Func<T>>
是表达式树形式的 lambda 表达式的表示;Func<T>
;ExpressionVisitor
;观察/翻译/修改表达式树Func<T>
;Expression<Func<T>>
.有一篇文章描述了代码示例的详细信息:
LINQ: Func<T> vs. Expression<Func<T>>。
希望它会有所帮助。
LINQ 是典型的例子(例如,与数据库交谈),但事实上,任何时候你更关心表达要做什么,而不是实际去做。例如,我在protobuf-net的 RPC 堆栈中使用这种方法(以避免代码生成等) - 所以你调用一个方法:
string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));
这将解构要解析的表达式树SomeMethod
(以及每个参数的值),执行 RPC 调用,更新任何ref
/ out
args,并返回远程调用的结果。这只能通过表达式树实现。我在这里更详细地介绍了这一点。
另一个示例是当您手动构建表达式树以编译为 lambda 时,如通用运算符代码所做的那样。
当您想将函数视为数据而不是代码时,您将使用表达式。如果您想操作代码(作为数据),您可以这样做。大多数情况下,如果您不需要表达式,那么您可能不需要使用表达式。
主要原因是您不想直接运行代码,而是想检查它。这可能有多种原因:
使用 LINQ-to-SQL 时,将Func<>
s 传递给Where()
orCount()
是不好的。真糟糕。如果您使用 a Func<>
then 它调用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()
在您使用类似or的执行方法之前,不会执行查询ToList()
。