Expression.Quote()做了什么,Expression.Constant()不能做什么?

时间:2010-09-15 09:49:29

标签: c# expression-trees

注意:我知道之前的问题“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节点类型被发明了?

5 个答案:

答案 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)

我认为这里的重点是树的表现力。包含委托的常量表达式实际上只包含恰好是委托的对象。这比直接分解为一元二元表达式更具表现力。