我理解lambdas和Func和Action委托。但表情 我头疼不已。
在什么情况下,你会使用表达式<Func<T>>而不是普通的旧Func<T>?
我理解lambdas和Func和Action委托。但表情 我头疼不已。
在什么情况下,你会使用表达式<Func<T>>而不是普通的旧Func<T>?
当前回答
这是我的两分钱…
Func<T> =一个刚被执行的委托/方法,仅此而已。 表达式<Func<T>> =转换为另一种形式。例如,LINQ to Entities表达式被转换为等效的SQL查询。
想象一下,两者看起来很相似,但表达式就像一个数据结构,具有反射的能力。编译器实际上知道关于它的签名和主体的所有信息(类似于类反射)。利用这些知识,表达式被转换为其他形式,就像LINQ被转换为SQL查询一样。
现在,还有另一个角度来处理IQueryable行为。建议您始终传递LINQ的Expression to Where或Count方法,以便您的查询过滤器在SQL server上运行,而不是在内存中提取数据然后进行过滤。
其他回答
当您希望将函数视为数据而不是代码时,可以使用表达式。如果您想操作代码(作为数据),可以这样做。大多数情况下,如果你认为不需要表达式,那么你可能就不需要使用表达式。
我添加了一个针对新手的答案,因为这些答案似乎超出了我的理解范围,直到我意识到它是多么简单。有时是你认为事情很复杂,这让你无法“理清思路”。
我不需要理解其中的区别,直到我遇到了一个非常烦人的“bug”,试图一般地使用LINQ-to-SQL:
public IEnumerable<T> Get(Func<T, bool> conditionLambda){
using(var db = new DbContext()){
return db.Set<T>.Where(conditionLambda);
}
}
这工作得很好,直到我开始在更大的数据集上得到outofmemoryexception。在lambda中设置断点使我意识到它正在逐一遍历表中的每一行,寻找与lambda条件匹配的条件。这让我困惑了一段时间,因为为什么它把我的数据表作为一个巨大的IEnumerable,而不是像它应该做的那样做LINQ-to-SQL ?它也在我的LINQ-to-MongoDb对应版本中做同样的事情。
修复方法是简单地将Func<T, bool>转换为表达式<Func<T, bool>>,所以我谷歌了为什么它需要一个表达式而不是Func,在这里结束。
表达式只是将委托转换为关于自身的数据。所以a => a + 1就变成了"左边有一个整数a,右边你给它加1 "就是这样。你现在可以回家了。它显然比这更有结构,但这本质上就是表达式树的全部——没有什么可以让你理解的。
Understanding that, it becomes clear why LINQ-to-SQL needs an Expression, and a Func isn't adequate. Func doesn't carry with it a way to get into itself, to see the nitty-gritty of how to translate it into a SQL/MongoDb/other query. You can't see whether it's doing addition or multiplication or subtraction. All you can do is run it. Expression, on the other hand, allows you to look inside the delegate and see everything it wants to do. This empowers you to translate the delegate into whatever you want, like a SQL query. Func didn't work because my DbContext was blind to the contents of the lambda expression. Because of this, it couldn't turn the lambda expression into SQL; however, it did the next best thing and iterated that conditional through each row in my table.
编辑:应约翰·彼得的要求,对我的最后一句话进行解释:
IQueryable extends IEnumerable, so IEnumerable's methods like Where() obtain overloads that accept Expression. When you pass an Expression to that, you keep an IQueryable as a result, but when you pass a Func, you're falling back on the base IEnumerable and you'll get an IEnumerable as a result. In other words, without noticing you've turned your dataset into a list to be iterated as opposed to something to query. It's hard to notice a difference until you really look under the hood at the signatures.
当您希望将lambda表达式视为表达式树并查看其内部而不是执行它们时。例如,LINQ to SQL获取表达式并将其转换为等效的SQL语句并将其提交给服务器(而不是执行lambda)。
Conceptually, Expression<Func<T>> is completely different from Func<T>. Func<T> denotes a delegate which is pretty much a pointer to a method and Expression<Func<T>> denotes a tree data structure for a lambda expression. This tree structure describes what a lambda expression does rather than doing the actual thing. It basically holds data about the composition of expressions, variables, method calls, ... (for example it holds information such as this lambda is some constant + some parameter). You can use this description to convert it to an actual method (with Expression.Compile) or do other stuff (like the LINQ to SQL example) with it. The act of treating lambdas as anonymous methods and expression trees is purely a compile time thing.
Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }
将有效地编译为一个IL方法,该方法得不到任何结果并返回10。
Expression<Func<int>> myExpression = () => 10;
将被转换为描述不获取参数并返回值10的表达式的数据结构:
大图
虽然它们在编译时看起来是一样的,但编译器生成的内容完全不同。
LINQ是典型的例子(例如,与数据库对话),但事实上,任何时候您更关心表达要做什么,而不是实际做什么。例如,我在protobuf-net的RPC堆栈中使用这种方法(以避免代码生成等)-所以你调用一个方法:
string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));
这将分解表达式树以解析SomeMethod(以及每个参数的值),执行RPC调用,更新任何ref/out参数,并从远程调用返回结果。这只能通过表达式树实现。我在这里讲得更多。
另一个例子是为了编译为lambda而手动构建表达式树,就像使用泛型操作符代码一样。
这里过于简化了,但Func是一个机器,而Expression是一个蓝图。: D