注意:我知道之前的问题“What is the purpose of LINQ's Expression.Quote method?”,但如果你继续阅读,你会发现它没有回答我的问题。
我理解Expression.Quote()
的陈述目的是什么。但是,Expression.Constant()
可用于相同目的(除Expression.Constant()
已用于的所有目的外)。因此,我不明白为什么Expression.Quote()
完全是必需的。
为了证明这一点,我写了一个简单的例子,其中人们通常会使用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#编译器将嵌套的lambda表达式编译为涉及Expression.Constant()
而不是Expression.Quote()
的表达式树,以及任何想要将表达式树处理成其他查询语言的LINQ查询提供程序(例如SQL) )可以查找类型为ConstantExpression
的{{1}},而不是Expression<TDelegate>
具有特殊UnaryExpression
节点类型的Quote
,其他所有内容都相同。
我错过了什么?为什么Expression.Quote()
和Quote
的特殊UnaryExpression
节点类型被发明了?
答案 0 :(得分:178)
答案 1 :(得分:18)
这个问题已经得到了很好的答案。我还想指出一个可以证明有关表达式树的问题的资源:
Microsoft有一个名为Dynamic Language Runtime的CodePlex项目。它的文档包括标题为"Expression Trees v2 Spec"的文档,正是如此:.NET 4中LINQ表达式树的规范。
例如,它说明了以下Expression.Quote
:
4.4.42报价
在UnaryExpressions中使用Quote来表示具有Expression类型的“常量”值的表达式。与Constant节点不同,Quote节点专门处理包含的ParameterExpression节点。如果包含的ParameterExpression节点声明了一个将在结果表达式中关闭的局部,则Quote会替换其参考位置中的ParameterExpression。在计算Quote节点的运行时,它将闭包变量引用替换为ParameterExpression引用节点,然后返回引用的表达式。 [...] (第63-64页)
答案 2 :(得分:1)
在得到一个非常好的答案之后,很明显语义是什么。尚不清楚为什么以这种方式设计,请考虑:
Expression.Lambda(Expression.Add(ps, pt));
当编译并调用此lambda时,它将评估内部表达式并返回结果。这里的内部表达式是一个加法运算,因此将评估 ps + pt 并返回结果。按照此逻辑,下面的表达式:
Expression.Lambda(
Expression.Lambda(
Expression.Add(ps, pt),
pt), ps);
在调用外部lambda时应返回内部的lambda编译方法引用(因为我们说过lambda编译为方法引用)。那么,为什么我们需要报价?!为了区分返回方法引用和引用调用结果的情况。
特别是:
let f = Func<...>
return f; vs. return f(...);
由于某种原因,.Net设计人员在第一种情况下选择 Expression.Quote(f),在第二种情况下选择普通的 f 。 我认为这会造成很多混乱,因为在大多数编程语言中,返回值是直接的(不需要 Quote 或任何其他操作),但是调用确实需要额外的编写(括号+参数)。 ,即在MSIL级别转换为调用。 .Net设计师对表达式树则相反。知道原因会很有趣。
答案 3 :(得分:0)
我相信它更像是给定的:
Expression<Func<Func<int>>> f = () => () => 2;
您的树是Expression.Lambda(Expression.Lambda)
,而f
代表lambda的表达式树,该lambda返回Func<int>
,返回2
。
但是,如果您想要的是返回 Expression Tree 的lambda,而返回2
的lambda,则您需要:
Expression<Func<Expression<Func<int>>>> f = () => () => 2;
现在您的树是Expression.Lambda(Expression.Quote(Expression.Lambda))
,而f
代表一个lambda的表达式树,该lambda返回一个Expression<Func<int>>
,它是一个Func<int>
的表达式树,该树返回了{{ 1}}。
答案 4 :(得分:-2)
我认为这里的重点是树的表现力。包含委托的常量表达式实际上只包含恰好是委托的对象。这比直接分解为一元二元表达式更具表现力。