简短答案:
该帖运算符是一个运营商,其诱导其操作关闭的语义。常数就是值。
引号和常量具有不同的含义,因此在表达式树中具有不同的表示形式。对于两种截然不同的事物,具有相同的表示形式是非常令人困惑和容易出错的。
长答案:
考虑以下:
(int s)=>(int t)=>s+t
外层Lambda是绑定到外层Lambda参数的加法器的工厂。
现在,假设我们希望将其表示为表达式树,稍后将对其进行编译和执行。表达式树的主体应该是什么?这取决于您是否希望已编译状态返回委托或表达式树。
让我们从消除无趣的案例开始。如果我们希望它返回一个委托,那么关于使用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作为对该函数的委托,该函数在为外部lambda生成的函数的状态上处于关闭状态。我们不再需要考虑这种情况。
假设我们希望编译后的状态返回一个表达式树内部。有两种方法可以做到:简单方法和困难方法。
很难的方法是说
(int s)=>(int t)=>s+t
我们真正的意思是
(int s)=>Expression.Lambda(Expression.Add(...
然后为此生成表达式树,从而产生混乱:
Expression.Lambda(
Expression.Call(typeof(Expression).GetMethod("Lambda", ...
等等等等,用数十行反射代码来制作lambda。 quote操作符的目的是告诉表达式树编译器,我们希望将给定的lambda视为表达式树而不是函数,而不必显式生成表达式树生成代码。
简单的方法是:
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));
确实,如果编译并运行此代码,您将获得正确的答案。
请注意,quote运算符是在内部lambda上引入闭包语义的运算符,该内部lambda使用外部变量(即外部lambda的形式参数)。
问题是:为什么不消除Quote并使它做同样的事情?
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));
该常量不会引起闭包语义。为什么要这样 您说这是一个常数。这只是一个价值。交给编译器时应该是完美的。编译器应该能够只将该值转储到需要它的堆栈中。
由于没有引发闭包,因此在调用时会出现“未定义'System.Int32'类型的变量's'”异常。
(此外:我刚刚检查了用于从带引号的表达式树创建委托的代码生成器,不幸的是,我在2006年向代码中添加的注释仍然存在。仅供参考,当用引号引起来时,提升的外部参数会快照成常量。表达式树被运行时编译器作为委托进行了实证化,这是有充分的理由使我以这种方式编写代码,但现在还不记得,但是这样做确实带来了对值引入闭包的讨厌的副作用。对外部参数的而不是封闭变量。显然,继承了该代码的团队决定不修复该缺陷,因此,如果您依赖于在已编译的带引号的内部lambda中观察到的封闭外部参数的变异,您将感到失望。但是,由于(1)突变形式参数和(2)依赖于外部变量的突变都是非常糟糕的编程习惯,所以我建议您更改程序以不使用这两种不好的编程习惯,而不是等待似乎不会出现的修复。对此错误表示歉意。)
因此,重复这个问题:
可以使C#编译器将嵌套的lambda表达式编译为一个包含Expression.Constant()而不是Expression.Quote()的表达式树,以及任何想要将表达式树处理为其他查询语言(例如SQL)的LINQ查询提供程序)可以查找类型为Expression的ConstantExpression而不是具有特殊Quote节点类型的UnaryExpression,其他所有内容都相同。
你是对的。通过使用常量表达式的类型作为标志,我们可以对语义信息进行编码,这意味着“在该值上产生闭包语义”。
那么,“常数”的含义是“使用该常数值,除非该类型恰好是表达式树类型,并且该值是有效的表达式树,在这种情况下,请使用通过重写给定表达式树的内部,以在我们现在可能处于的任何外部lambda的上下文中引发闭包语义。
但为什么会我们这样做疯狂的事?quote运算符是一个非常复杂的运算符,如果要使用它,则应明确使用它。您建议为了避免在已经存在的数十种方法中不添加任何额外的工厂方法和节点类型,我们在常量中添加了一个奇异的转角案例,以便常量有时在逻辑上是常量,而有时被重写具有关闭语义的lambda。
常量并不意味着“使用此值”也会有些奇怪。假设出于某种奇怪的原因,您希望上面的第三种情况将表达式树编译成一个委托,该委托分发了一个表达式树,该树具有对外部变量的未重写引用?为什么?也许是因为您正在测试编译器,并且只想传递常量,以便稍后可以对其进行其他分析。你的建议将使那不可能。无论如何,碰巧是表达式树类型的任何常量都将被重写。人们有一个合理的期望,即“常数”表示“使用此值”。“常量”是一个“按我说的做”节点。恒定处理器
并注意当然,你现在把理解的负担(即,理解是不断有复杂的语义,在一个案件平均“不变”和“诱导封闭语义”的基础上的标志是在类型系统)在每不仅对Microsoft提供程序进行表达式树语义分析的提供程序。有多少第三方提供商会出错?
“ Quote”挥舞着一个大红色的标语,上面写着:“嘿,伙计,看看这里,我是一个嵌套的lambda表达式,如果我在一个外部变量上封闭的话,我的语言会很古怪!” 而“常数”则说“我不过是一种价值;请根据需要使用我。” 当某些事情变得复杂而危险时,我们希望使其成为红色标志,而不是通过使用户深入研究类型系统来发现该值是否为特殊值,从而隐藏该事实。
此外,避免冗余甚至是目标的想法是错误的。当然,避免不必要的,令人困惑的冗余是一个目标,但是大多数冗余是一件好事。冗余可以提高清晰度。新的工厂方法和节点类型很便宜。我们可以根据需要创建任意数量的对象,以便每个对象干净地代表一个操作。我们无需诉诸“这样的恶作剧”,例如“这意味着一件事,除非将此字段设置为该事物,在这种情况下,这意味着其他事情。”