Expression.Quote()是做什么的Expression.Constant()不能已经做?

注意:我知道前面的问题“ LINQ的Expression.Quote方法的目的是什么? ,但是如果你阅读,你会看到它不能回答我的问题。

我明白Expression.Quote()目的是什么。 但是,可以将Expression.Constant()用于相同的目的(除了已经使用了Expression.Constant()所有用途之外)。 因此,我不明白为什么Expression.Quote()完全需要。

为了certificate这一点,我写了一个快速示例,其中通常使用Quote (请参阅标有感叹号的行),但是我使用了Constant ,而且效果相当好:

 string[] array = { "one", "two", "three" }; // This example constructs an expression tree equivalent to the lambda: // str => str.AsQueryable().Any(ch => ch == 'e') Expression<Func<char, bool>> innerLambda = ch => ch == 'e'; var str = Expression.Parameter(typeof(string), "str"); var expr = Expression.Lambda<Func<string, bool>>( Expression.Call(typeof(Queryable), "Any", new Type[] { typeof(char) }, Expression.Call(typeof(Queryable), "AsQueryable", new Type[] { typeof(char) }, str), // !!! Expression.Constant(innerLambda) // <--- !!! ), str ); // Works like a charm (prints one and three) foreach (var str in array.AsQueryable().Where(expr)) Console.WriteLine(str); 

expr.ToString()的输出也是一样的(无论是使用Constant还是Quote )。

鉴于上述观察,看起来Expression.Quote()是多余的。 C#编译器可以将嵌套的lambdaexpression式编译到涉及Expression.Constant()而不是Expression.Quote()的expression式树中,以及任何想要将expression式树处理成其他查询语言(如SQL )可以看出一个types为Expression<TDelegate>ConstantExpression ,而不是带有特殊的Quotetypes的UnaryExpression ,其他的都是一样的。

我错过了什么? 为什么Expression.Quote()UnaryExpression的特殊Quote节点types发明了?

简短的回答:

引号运算符是一个操作符 ,它引发操作数的闭包语义 。 常量只是值。

引号和常量具有不同的含义 ,因此在expression式树中具有不同的表示forms 。 对于两个截然不同的东西具有相同的表示方式是非常容易混淆和易受攻击的

很长的回答:

考虑以下:

 (int s)=>(int t)=>s+t 

外部lambda是绑定到外部lambda参数的加法器的工厂。

现在,假设我们希望将其表示为expression式树,稍后将被编译和执行。 表情树的身体应该是什么? 这取决于你是否想要编译状态返回一个委托或expression式树。

我们首先解释无趣的案例。 如果我们希望它返回一个委托,那么是否使用Quote或Constant是一个有争议的问题:

  var ps = Expression.Parameter(typeof(int), "s"); var pt = Expression.Parameter(typeof(int), "t"); var ex1 = Expression.Lambda( Expression.Lambda( Expression.Add(ps, pt), pt), ps); var f1a = (Func<int, Func<int, int>>) ex1.Compile(); var f1b = f1a(100); Console.WriteLine(f1b(123)); 

lambda有一个嵌套的lambda; 编译器生成内部的lambda作为一个函数的委托,该函数closures了为外部lambda生成的函数的状态。 我们不需要再考虑这种情况了。

假设我们希望编译状态返回内部的expression式树 。 有两种方法可以做到这一点:简单的方法和困难的方法。

困难的方式是说,而不是

 (int s)=>(int t)=>s+t 

我们真正的意思是

 (int s)=>Expression.Lambda(Expression.Add(... 

然后为此生成expression式树,产生这个混乱

  Expression.Lambda( Expression.Call(typeof(Expression).GetMethod("Lambda", ... 

等等等等,几十行reflection代码来制作lambda。 引用运算符的目的是告诉expression式树编译器,我们希望将给定的lambda视为expression式树而不是函数,而不必显式地生成expression式树生成代码

简单的方法是:

  var ex2 = Expression.Lambda( Expression.Quote( Expression.Lambda( Expression.Add(ps, pt), pt)), ps); var f2a = (Func<int, Expression<Func<int, int>>>)ex2.Compile(); var f2b = f2a(200).Compile(); Console.WriteLine(f2b(123)); 

事实上,如果你编译并运行这个代码,你会得到正确的答案。

请注意,引号运算符是在使用外部variables(外部lambda的forms参数)的内部lambda上引发闭包语义的运算符。

问题是:为什么不消除报价,并做同样的事情呢?

  var ex3 = Expression.Lambda( Expression.Constant( Expression.Lambda( Expression.Add(ps, pt), pt)), ps); var f3a = (Func<int, Expression<Func<int, int>>>)ex3.Compile(); var f3b = f3a(300).Compile(); Console.WriteLine(f3b(123)); 

常量不会引发闭包语义。 为什么要这样? 你说这是一个常数 。 这只是一个价值。 它应该是完美的交给编译器; 编译器应该能够生成该值的转储到需要的堆栈。

由于没有引发闭包,如果你这样做,你会得到一个“variables”的types“System.Int32”没有定义“的调用exception。

(另外:我刚刚从引用的expression式树中复习了代码生成器的代码生成器,不幸的是我在2006年把代码添加到代码中的评论仍然存在。仅供参考,当被引用的时候,被提升的外部参数被快照到常量expression式树被运行时编译器委托为委托,这是我编写代码的一个很好的理由,我不记得在这个时刻,但是它有引入闭包超过外部参数的讨厌副作用而不是closuresvariables ,显然inheritance这个代码的团队决定不修正这个缺陷,所以如果你依赖于在被编译的引用内部lambda中观察到的封闭外部参数的变化,你会感到失望。但是,由于(1)改变forms参数和(2)依赖于外部variables的变异是一个相当不好的编程实践,我build议你改变你的程序不要使用这两个不好的 编程的做法,而不是等待一个似乎不是即将到来的修复。 抱歉的错误。)

因此,重复这个问题:

C#编译器可以将嵌套的lambdaexpression式编译到涉及Expression.Constant()而不是Expression.Quote()的expression式树中,以及任何想要将expression式树处理成其他查询语言(如SQL )可以寻找一个types为Expression的ConstantExpression而不是具有特殊的Quotetypes的UnaryExpression,其他的都是一样的。

你是对的。 我们可以 使用常量expression式的types作为标志 对语义信息进行编码,这意味着“在这个值上引发闭包语义”。

然后,“常量”的意思是“使用这个常数值, 除非types恰好是expression式树types, 并且该值是一个有效的expression式树,在这种情况下,而是使用重写给定expression式树的内部,以便在我们现在可能正在使用的任何外层lambda的上下文中引发闭包语义。

但是我们为什么要做那件疯狂的事情? quote运算符是一个非常复杂的运算符 ,如果要使用它,应该明确地使用它。 你build议为了节省一些额外的工厂方法和节点types在已经存在的几十个之间,我们给常量添加了一个奇怪的angular落案例,所以常量有时在逻辑上是常量,有时它们被重写带闭包语义的lambdas。

常数并不意味着“使用这个值”也会有些奇怪的效果。 假设出于一些奇怪的原因,你想要上面的第三种情况,将expression式树编译成一个委托,它传递一个expression式树,这个expression式树有一个未被重写的对外部variables的引用? 为什么? 也许是因为你正在testing你的编译器,并且只想传递这个常量,以便你稍后可以对它进行一些其他的分析。 你的build议会使这个不可能。 任何碰巧是expression式树的常量都会被重写。 人们有一个合理的期望,即“常量”是指“使用这个值”。 “常量”是一个“做我说的”节点。 恒定的处理器的工作不是根据types来猜测你的意思

当然,注意你现在把理解的负担(也就是理解常量在一种情况下意味着“常量”的复杂语义,并且基于types系统中的一个标志来引起“闭包语义”)提供程序不仅对Microsoft提供程序进行expression式树的语义分析。 有多less第三方供应商会出错?

“引用”正在挥舞着一个大红旗,上面写着“嘿伙计,看看这里,我是一个嵌套的lambdaexpression式,如果我closures了一个外部variables,我就有古怪的语义! 而“常量”是说“我只不过是一种价值;按你认为合适的方式使用我”。 当某些事情是复杂而危险的时候,我们希望使它成为红旗,而不是通过让用户挖掘types系统来隐藏这个事实,以便找出这个值是否是特殊的。

而且,避免冗余的想法甚至是一个目标是不正确的。 当然,避免不必要的冗余是一个目标,但是多数冗余是一件好事; 冗余创造清晰度。 新的工厂方法和节点种类便宜 。 我们可以根据需要做出尽可能多的代表,使每一个代表一个干净的操作。 我们没有必要诉诸“这意味着一件事,除非这个领域是这个东西,在这种情况下,这意味着别的东西”这样的讨厌的技巧。

这个问题已经得到了很好的回答。 我还想指出一个可以certificate对expression式树有帮助的资源:

微软有一个名为Dynamic Language Runtime的CodePlex项目。 它的文档包括标题为“Expression Trees v2 Spec”的文档,这正是:.NET 4中LINQexpression式树的规范。

例如,它说下面关于Expression.Quote

4.4.42报价

在UnaryExpressions中使用引号来表示具有Expressiontypes的“常数”值的expression式。 与Constant节点不同,Quote节点特别处理包含的ParameterExpression节点。 如果包含的ParameterExpression节点声明将在结果expression式中closures的本地,则Quote将replace参考位置中的ParameterExpression。 在运行时计算Quote节点时,它会将ParameterExpression引用节点的闭包variables引用replace为引用expression式,然后返回带引号的expression式。 […] (第63-64页)

我认为这里的重点是树的performance力。 包含委托的常量expression式实际上只是包含恰好是委托的对象。 这比直接分解为一元和二元expression式的expression力要弱。