Expression.Reduce() 的作用是什么?

问题描述 投票:0回答:6

我已经使用表达式树几天了,我很想知道 Expression.Reduce() 的作用。 msdn 文档 不是很有帮助,因为它只声明它“减少”了表达式。为了以防万一,我尝试了一个示例(见下文)来检查该方法是否包含数学归约,但情况似乎并非如此。

有人知道这个方法的作用吗?是否可以提供一个快速示例来展示它的实际效果?有什么好的资源吗?

static void Main(string[] args)
{
    Expression<Func<double, double>> func = x => (x + x + x) + Math.Exp(x + x + x);
    Console.WriteLine(func);
    Expression r_func = func.Reduce();
    Console.WriteLine(r_func); // This prints out the same as Console.WriteLine(func)
}
c# .net lambda expression-trees
6个回答
35
投票

您需要查看的文档是expr-tree-spec.pdf

这是表达式树的规范。阅读“2.2 可约化节点”和“4.3.5约化方法”部分。

基本上,此方法适用于将动态语言实现或移植到 .NET 的人们。这样他们就可以创建自己的节点,这些节点可以“简化”为标准表达式树节点并且可以编译。表达式树 API 中有一些“可简化”节点,但我不知道您是否可以获得任何实际示例(因为所有标准表达式节点无论如何都会编译,作为最终用户,您可能不关心它们是否“简化”) “无论是否在幕后)。

是的,MSDN 文档在该领域非常基础,因为语言实现者的信息和文档的主要来源位于 GitHub,文档位于 其自己的子文件夹


25
投票

稍微反汇编一下,我发现 Expression.CanReduce 总是返回

false
而 Expression.Reduce() 总是返回
this
。然而,有一些类型可以覆盖这两者。 LambdaExpression 继承了默认实现,这解释了为什么到目前为止已经尝试过的表达式不起作用。

重写Reduce()的类型之一是MemberInitExpression,它使我进行了以下成功的实验:

class ReduceFinder : ExpressionVisitor {
    public override Expression Visit(Expression node) {
        if (node != null && node.CanReduce) {
            var reduced = node.Reduce();
            Console.WriteLine("Found expression to reduce!");
            Console.WriteLine("Before: {0}: {1}", node.GetType().Name, node);
            Console.WriteLine("After: {0}: {1}", reduced.GetType().Name, reduced);
        }
        return base.Visit(node);
    }
}

class Foo {
    public int x;
    public int y;
}

static class Program {
    static void Main() {
        Expression<Func<int, Foo>> expr = z => new Foo { x = (z + 1), y = (z + 1) };
        new ReduceFinder().Visit(expr);
    }
}

输出:

Found expression to reduce!  
Before: MemberInitExpression: new Foo() {x = (z + 1), y = (z + 1)}  
After: ScopeN: { ... }  

9
投票

这是一个相当老的问题,但似乎有点有趣,所以我添加了这个额外的响应,其中包含关于开箱即用的 .NET 东西目前的功能的信息。

据我所知,Reduce() 仅在将分配作为工作一部分实现的复杂操作中被重写。似乎有三个关键场景。

  1. 复合赋值扩展到离散二进制算术和赋值运算;换句话说,

    x += y

    成为

    x = x + y

  2. 预自增和后自增运算符被扩展为离散运算。对于预递增/递减,

    ++x

    变为大约:

    x = x + 1

    x++

    变为大约:

    temp = x;
    x = x + 1;
    temp;
    

    我之所以这么说,是因为该操作不是作为二元运算

    x + 1
    实现的,左操作数是
    x
    ,右操作数是常量
    1
    ,而是作为一元递增/递减运算实现。最终效果是一样的。

  3. 成员和列表初始值设定项从短格式扩展为长格式。所以:

    new Thing() { Param1 = 4, Param2 = 5 }

    变成:

    temp = new Thing();
    temp.Param1 = 4;
    temp.Param2 = 5;
    temp;
    

    和:

    new List<int>() { 4, 5 }

    变成:

    temp = new List<int>();
    temp.Add(4);
    temp.Add(5);
    temp;
    

这些变化是否使人们更容易或更难地实现解析表达式树的东西是一个观点问题,但底线是这似乎是 .NET 框架中开箱即用的减少水平。


4
投票

进一步回答Nick Guerrera,我发现以下表达式覆盖了

CanReduce
方法:

* 表示根据 JustDecompile

 的内部派生类型 
BinaryExpression


1
投票

我猜测不同的 linq 提供者更多地使用它们将某些节点类型转换为更简单的 ast 表示形式。

由于文档很少,可以用于公共子表达式消除来消除冗余表达式。如果您的函数多次计算 x+x 而不更改本地 x,您可以通过将第一个表达式的结果保存到临时表达式中来简化它。也许 linq 提供者可以选择性地实现这些转换。

或者如果您嵌套了不包含代码的BlockExpressions(像 {{{}}} 这样的表达式),则可以消除这些代码,或者空的 ConditionalExpression...


0
投票

给定 Expression.Not(Expression.Not(expr))

Reduce() 会消除双重否定吗?

例如,

布尔x=真; x ==!!x

© www.soinside.com 2019 - 2024. All rights reserved.