在编译器生成的表达式树中使用表达式 [英] Using an Expression in a compiler-generated expression tree

查看:82
本文介绍了在编译器生成的表达式树中使用表达式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道我可以使用以下方式创建表达式树

I know I can create expression trees using:

  1. 工厂方法.

  1. Factory methods.

lambda表达式到Expression的编译器转换.

Compiler conversion of a lambda expression to an Expression.

对于复杂的表达式树,我更喜欢2,因为它更简洁.

For complicated expression trees I would prefer 2 because it is more concise.

是否可以使用这种方式引用已经构造的Expressions?

Is it possible to refer to already constructed Expressions using this way?

using System;
using System.Linq.Expressions;

public class Test
{
    public static Expression<Func<int, int>> Add(Expression expr)
    {
#if false
        // works
        ParameterExpression i = Expression.Parameter(typeof(int));
        return Expression.Lambda<Func<int, int>>(Expression.Add(i, expr), i);
#else
        // compiler error, can I pass expr here somehow?
        return i => i + expr;
#endif
    }

    public static void Main()
    {
        Func<int, int> f = Add(Expression.Constant(42)).Compile();
        Console.WriteLine(f(1));
    }
}

推荐答案

您不能将任意Expression实例与编译时表达式树混合使用. 可以做的是构造一个替换了特定节点的新表达式树,因此您可以先i => i + marker然后构造一个新树,将marker节点替换为您的运行时表达式.这需要编写适当的ExpressionVisitor:

You can't mix arbitrary Expression instances with compile-time expression trees. What you can do is construct a new expression tree with specific nodes replaced, so you can have i => i + marker and then construct a new tree with the marker node replaced by your runtime expression. This requires writing an appropriate ExpressionVisitor:

public static class ExpressionExtensions {
  public static T AsPlaceholder<T>(this Expression expression) {
    throw new InvalidOperationException(
      "Expression contains placeholders."
    );
  }

  public static Expression FillPlaceholders(this Expression expression) {
    return new PlaceholderExpressionVisitor().Visit(expression);
  }
}

class PlaceholderExpressionVisitor : ExpressionVisitor {
  protected override Expression VisitMethodCall(MethodCallExpression node) {
    if (
      node.Method.DeclaringType == typeof(ExpressionExtensions) && 
      node.Method.Name == "AsPlaceholder"  // in C# 6, we would use nameof()
    ) {
      return Expression.Lambda<Func<Expression>>(node.Arguments[0]).Compile()();
    } else {
      return base.VisitMethodCall(node);
    }
  }
}

Add现在变为:

public static Expression<Func<int, int>> Add(Expression expr) {
    Expression<Func<int, int>> add = i => i + expr.AsPlaceholder<int>();
    return (Expression<Func<int, int>>) add.FillPlaceholders();
}

有点神秘的表情

Expression.Lambda<Func<Expression>>(node.Arguments[0]).Compile()()

可以通过观察

来解释,观察到编译器将捕获我们要插入到闭包中的表达式,而不管它来自何处,因此方法调用的参数始终是对此闭包的引用,我们需要对该闭包进行求值得到实际的表达.

can be explained by observing that the compiler will capture the expression we're inserting in a closure, regardless of where it came from, so the argument of the method call is always a reference to this closure that we need to evaluate to get at the actual expression.

这会扩展到任意数量的替换表达式,并且需要最少的显式键入.

This scales to an arbitrary number of replacement expressions with a minimum of explicit typing.

这篇关于在编译器生成的表达式树中使用表达式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆