【已更新最新开发文章,点击查看详细】

这可能是为了添加日志记录、拦截方法调用并跟踪它们,或其他目的。

转换即访问

新树可包含对原始节点的引用或已放置在树中的新节点。

我们通过将常数节点替换为执行乘法运算的新节点来进行此替换,而不必阅读常数的值并将其替换为新的常数。

此处,在找到常数节点后,创建新乘法节点(其子节点是原始常数和常数 10):

private static Expression ReplaceNodes(Expression original)
{
    if (original.NodeType == ExpressionType.Constant)
    {
        return Expression.Multiply(original, Expression.Constant(10));
    }
    else if (original.NodeType == ExpressionType.Add)
    {
        var binaryExpression = (BinaryExpression)original;
        return Expression.Add(ReplaceNodes(binaryExpression.Left),
                              ReplaceNodes(binaryExpression.Right));
    }
    return original;
}

可以通过编译并执行替换的树对此进行验证。

var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
var addition = Expression.Add(one, two);
var sum = ReplaceNodes(addition);
var executableFunc = Expression.Lambda(sum);

var func = (Func<int>)executableFunc.Compile();
var answer = func();
Console.WriteLine(answer);

生成新树是两者的结合:访问现有树中的节点,和创建新节点并将其插入树中。

由于不能修改节点,因此可以在需要时随时重用相同的节点。

遍历并执行加法
对于加法表达式,遍历这些树后,其结果为左操作数和右操作数的总和。
 
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
var three= Expression.Constant(3, typeof(int));
var four = Expression.Constant(4, typeof(int));
var addition = Expression.Add(one, two);
var add2 = Expression.Add(three, four);
var sum = Expression.Add(addition, add2);

// 声明委托,这样就可以从它本身递归地调用它
Func<Expression, int> aggregate = null;
// 聚合、返回常量或左、右操作数之和。
// 主要简化:假设每个二进制表达式都是一个加法。
aggregate = (exp) => exp.NodeType == ExpressionType.Constant 
? (int)((ConstantExpression)exp).Value
: aggregate(((BinaryExpression)exp).Left)
+ aggregate(((BinaryExpression)exp).Right); var theSum = aggregate(sum); Console.WriteLine(theSum);

可以通过在调试器中运行示例并跟踪执行来跟踪执行。

下面是包含大量跟踪信息的聚合方法的更新版本:

private static int Aggregate(Expression exp)
{
    if (exp.NodeType == ExpressionType.Constant)
    {
        var constantExp = (ConstantExpression)exp;
        Console.Error.WriteLine($"Found Constant: {constantExp.Value}");
        return (int)constantExp.Value;
    }
    else if (exp.NodeType == ExpressionType.Add)
    {
        var addExp = (BinaryExpression)exp;
        Console.Error.WriteLine("Found Addition Expression");
        Console.Error.WriteLine("Computing Left node");
        var leftOperand = Aggregate(addExp.Left);
        Console.Error.WriteLine($"Left is: {leftOperand}");
        Console.Error.WriteLine("Computing Right node");
        var rightOperand = Aggregate(addExp.Right);
        Console.Error.WriteLine($"Right is: {rightOperand}");
        var sum = leftOperand + rightOperand;
        Console.Error.WriteLine($"Computed sum: {sum}");
        return sum;
    }
    else throw new NotSupportedException("Haven't written this yet");
}

在同一表达式中运行该版本将生成以下输出:

10
Found Addition Expression
Computing Left node
Found Addition Expression
Computing Left node
Found Constant: 1
Left is: 1
Computing Right node
Found Constant: 2
Right is: 2
Computed sum: 3
Left is: 3
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 3
Left is: 3
Computing Right node
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 10
10

应当能够看出代码如何在遍历树的同时访问代码和计算总和,并得出总和。

现在,让我们来看看另一个运行,其表达式由 sum1 给出:

Expression<Func<int> sum1 = () => 1 + (2 + (3 + 4));

下面是通过检查此表达式得到的输出:

Found Addition Expression
Computing Left node
Found Constant: 1
Left is: 1
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 2
Left is: 2
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 3
Left is: 3
Computing Right node
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 9
Right is: 9
Computed sum: 10
10

节点的访问顺序不同,因为树是以首先发生的不同运算构造的。

限制

实际上,这意味着在引入新语言功能时,解释表达式树的代码将仍可能照常运行。

它是一种功能强大的工具,作为 .NET 生态系统的一种功能,它可使丰富的库(如实体框架)完成其所执行的操作。

【已更新最新开发文章,点击查看详细】

相关文章: