我了解兰姆达斯和Func和Action代表。但是表情让我很困惑。在什么情况下,您会使用Expression>而不是普通的旧Func?
- func<>将转换为c编译器级别上的方法,表达式>将在直接编译代码后在msil级别上执行,这就是它更快的原因。
- 除了答案之外,CSharp语言规范"4.6表达式树类型"还有助于交叉引用
当您希望将lambda表达式视为表达式树并在其中查找而不是执行它们时。例如,Linq to SQL获取表达式并将其转换为等效的SQL语句并将其提交给服务器(而不是执行lambda)。
从概念上讲,Expression>与Func完全不同。Func表示一个delegate相当于一个方法的指针,Expression>表示lambda表达式的树数据结构。此树结构描述lambda表达式所做的,而不是实际执行的操作。它基本上保存了有关表达式、变量、方法调用等组成的数据。(例如,它保存诸如lambda是某个常量+某个参数等信息)。您可以使用此描述将其转换为实际方法(使用Expression.Compile)或使用它执行其他操作(如linq to sql示例)。将lambda视为匿名方法和表达式树的行为纯粹是编译时的事情。
1
| Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; } |
将有效地编译为不获取任何内容并返回10的IL方法。
1
| Expression<Func<int>> myExpression = () => 10; |
将转换为数据结构,该结构描述一个不获取参数并返回值10的表达式:
大图像
虽然它们在编译时看起来都一样,但编译器生成的是完全不同的。
- 所以,换句话说,Expression包含关于某个委托的元信息。
- @事实上,贝尔特,不。代表根本不参与。与委托有任何关联的原因是,您可以将表达式编译为委托,或者更精确地说,将表达式编译为方法,并将该方法的委托作为返回值。但是表达式树本身就是数据。当您使用Expression>而不仅仅是Func<...>时,代理不存在。
- 你说得对@luaan。我无意在Expressions和代表之间建立任何人为的关系。我的观点与最初的问题有关,即"一个Expression>有什么,一个Func没有什么?"我只是想用一种非常简短的方式来总结一下被接受的答案,不过听起来可能有点过于简单了。
- 此元数据非常有用,因此您可以将C查询转换为任何其他语言的查询。例如,您可以将C StartsWith()转换为SQL LIKE '%etc'。参见codethinked.com/taking-the-magic-out-of-expression
- 什么使它成为一棵树?树枝在哪里?
- @Kyle Delaney (isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }这样的表达式是一个表达式树,为if语句创建分支。
- @Bertl委托是CPU看到的(一个体系结构的可执行代码),表达式是编译器看到的(只是源代码的另一种格式,但仍然是源代码)。
- @Bertl:可以更准确地概括为,表达式对func的意义就像StringBuilder对字符串的意义一样。它不是一个字符串/func,但它包含在被要求创建时所需的数据。
我在为noobs添加一个答案,因为这些答案似乎在我的脑海中浮现,直到我意识到这是多么的简单。有时是你的期望,它是复杂的,使你不能'绕着它的头'。
直到我遇到一个非常恼人的"bug",试图使用Linq to SQL,我才需要理解这种区别:
1 2 3 4 5
| public IEnumerable <T > Get(Func <T, bool> conditionLambda ){
using(var db = new DbContext ()){
return db .Set<T >.Where(conditionLambda );
}
} |
这非常有效,直到我开始从内存中获得更大数据集的例外。在lambda中设置断点使我意识到它正在逐个遍历表中的每一行,以查找与lambda条件匹配的项。这让我困惑了一段时间,因为它为什么要把我的数据表当作一个巨大的IEnumerable而不是像预期的那样执行LinqToSQL?在我的linq-to-mongodb版本中,它也做了同样的事情。
修复方法只是把Func变成Expression>,所以我在谷歌上搜索为什么它需要Expression而不是Func,最后到了这里。
表达式只是将委托转换为有关其自身的数据。所以a => a + 1变成了"左边有一个int a"。在右边你加了一个。"就这样。你现在可以回家了。它显然比这更具结构性,但本质上所有的表达式树都是——没有什么可以环绕你的头。
了解到这一点,很清楚为什么linq to sql需要一个Expression,而Func不足够。Func没有一种方法可以深入了解自己,了解如何将其转换为SQL/MongoDB/其他查询的细节。你看不出它是在做减法的加法还是乘法。你所能做的就是运行它。另一方面,Expression允许您查看委托内部,查看它想要做的一切,使您能够将其转换为您想要的任何内容,比如SQL查询。Func没有工作,因为我的dbContext对lambda表达式中的实际内容视而不见,无法将其转换为SQL,所以它做了第二件最好的事情,并通过表中的每一行迭代了该条件。
编辑:根据约翰·彼得的要求,对我最后一句话进行阐述:
iQueryable扩展了IEnumerable,因此IEnumerable的方法(如Where()获得接受Expression的重载。当你把一个Expression传递给它时,结果你会保持一个iqueueryable,但是当你把一个Func传递给它时,你会回到ienumerable的基础上,结果你会得到一个ienumerable。换句话说,在没有注意到的情况下,您已经将数据集转换为要迭代的列表,而不是要查询的内容。在你真正看到签名之前,很难发现有什么不同。
- chad;请再详细解释一下这个注释:"func不起作用,因为我的dbContext对lambda表达式中的实际内容视而不见,无法将其转换为sql,所以它做了第二件最好的事情,并在我的表中的每一行重复该条件。"
- 这是我在这篇文章中看到的最好的答案。谢谢您,先生!
- 在外面的所有角落谢谢
- 感谢您在传递表达式和func时解释不同的行为。知道这一点非常重要!!
- 回答很好,谢谢!这应该得到更多的赞成票!
- >函数…你所能做的就是运行它。这并不完全正确,但我认为这是应该强调的一点。运行函数/动作,分析表达式(在运行之前,甚至不是运行之前)。
- 非常感谢,查德!
- @查德是这里的问题吗?:db.set查询了所有数据库表以及之后的表,因为.where(conditionlambda)使用了where(ienumerable)扩展方法,该方法对内存中的整个表进行枚举。我认为您可以摆脱memoryException,因为这段代码试图将整个表加载到内存中(当然还创建了对象)。我说的对吗?谢谢)
在选择表达式vs func时,一个非常重要的考虑因素是,像linq to entities这样的iqueryable提供程序可以"消化"在表达式中传递的内容,但将忽略在func中传递的内容。我有两篇关于这个主题的博客文章:
关于表达式与带有实体框架和爱上Linq第7部分:表达和功能(最后一节)
- +我来解释。但是,我得到的"linq-to-entities"中不支持"invoke"类型的linq表达式节点。并且在获取结果后必须使用foreach。
我想补充一下关于Func和Expression>之间的区别:
- Func只是一个普通的老派多播代表;
- Expression>是lambda表达式的表达式树形式表示;
- 表达式树可以通过lambda表达式语法或API语法构造;
- 表达式树可以编译为委托Func;
- 逆向转换在理论上是可能的,但它是一种反编译,没有内置的功能,因为它不是一个简单的过程;
- 表达树可以通过ExpressionVisitor进行观察/翻译/修改;
- IEnumerable的扩展方法与Func一起使用;
- iQuery的扩展方法使用Expression>操作。
有篇文章用代码示例描述了详细信息:
linq:funcvs.expression
希望能有所帮助。
- 很好的列表,一个小注意事项是您提到逆转换是可能的,但是一个确切的逆转换是不可能的。在转换过程中丢失了一些元数据。但是,您可以将其反编译为表达式树,该树在再次编译时生成相同的结果。
从KrzysztofCwalina的书(框架设计指南:可重用.NET库的惯例、习惯用法和模式)中可以得到更多的哲学解释;
编辑非图像版本:
Most times you're going to want Func or Action if all that needs to happen is to run some code. You need Expression when the code needs to be analyzed, serialized, or optimized before it is run. Expression is for thinking about code, Func/Action is for running it.
- 放好。例如,当您希望将func转换为某种查询时,需要表达式。也就是说,你需要把database.data.Where(i => i.Id > 0)作为SELECT FROM [data] WHERE [id] > 0来执行。如果你只是通过一个func,你已经在你的驱动程序上加上了盲板,它所能做的就是SELECT *,然后一旦它把所有的数据加载到内存中,迭代每个数据并过滤掉id>0的所有数据。在Expression中包装Func使驱动程序能够分析Func并将其转换为SQL/MongoDB/其他查询。
- 链接似乎不起作用
- @KcasWolfrevo为非图像版本编辑。
- 因此,当我计划度假时,我会使用Expression,但当我度假时,它将是Func/Action;
- @这是我需要的最后一件作品。谢谢。我看这个已经有一段时间了,你在这里的评论使所有的研究点击。
LINQ是一个典型的例子(例如,与数据库交谈),但实际上,任何时候你更关心表达要做什么,而不是实际做什么。例如,我在protobuf net的rpc堆栈中使用这种方法(以避免代码生成等),因此您可以使用以下方法调用方法:
1
| string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...)); |
这将解构表达式树以解析SomeMethod(以及每个参数的值),执行rpc调用,更新任何ref/out参数,并返回远程调用的结果。这只能通过表达式树实现。我在这里再谈一谈。
另一个例子是,当您手动构建表达式树以编译为lambda时,就像一般运算符代码所做的那样。
当您希望将函数视为数据而不是代码时,可以使用表达式。如果要操作代码(作为数据),可以这样做。大多数情况下,如果您不需要表达式,那么您可能不需要使用表达式。
主要原因是当您不想直接运行代码,而是想检查它时。这有多种原因:
- 将代码映射到不同的环境(即实体框架中的C代码到SQL)
- 在运行时替换部分代码(动态编程,甚至是纯干技术)
- 代码验证(在模拟脚本或进行分析时非常有用)
- 序列化-表达式可以很容易和安全地序列化,委托不能
- 对非固有强类型的事物使用强类型安全性,并利用编译器检查,即使在运行时执行动态调用(使用Razor的ASP.NET MVC 5是一个很好的示例)
- 你能再详细讲一下5号吗
- @UOWZD01只是看看Razor——它广泛使用这种方法。
- @Luaan我正在寻找表达式序列化,但是如果没有有限的第三方使用,就找不到任何内容。.NET 4.5是否支持表达式树序列化?
- @瓦比不是我所知道的-这对一般情况来说也不是个好主意。我的观点是,您能够针对您希望支持的特定情况,针对提前设计的接口,编写非常简单的序列化——我已经做过几次了。在一般情况下,由于任何表达式都可以包含对任意委托/方法引用的调用,因此Expression与委托一样不可能序列化。""容易"当然是相对的。
- @谢谢你的澄清。
我还没有看到任何关于表演的答案。把Func<>s传给Where()或Count()是不好的。真坏。如果您使用Func<>,那么它将把IEnumerablelinq的内容称为IQueryable,这意味着整个表被拉入然后过滤。Expression>速度明显更快,尤其是在查询另一台服务器上的数据库时。
- 这是否也适用于内存查询?
- @STT106可能没有。
- 只有当您枚举列表时,这才是正确的。如果使用GetEnumerator或ForEach,则不会将IEnumerable完全加载到内存中。
- @STT106传递到list<>的.where()子句时,表达式>gets.compile()对其调用,因此func<>几乎肯定更快。请参阅referencesource.microsoft.com/system.core/system/linq/&hellip;