【问题标题】:Using an Expression in a compiler-generated expression tree在编译器生成的表达式树中使用表达式
【发布时间】:2014-11-18 16:40:45
【问题描述】:

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

  1. 工厂方法。

  2. 编译器将 lambda 表达式转换为 Expression

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

这样可以引用已经构建好的Expressions吗?

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 到 Expression 的添加中指定表达式的类型,因为我认为这就是上面示例中发生的情况,但它是推断出来的。

标签: c# linq


【解决方案1】:

没有任何开箱即用的功能,但您可以自己构建一个工具来提供此功能。

您可以编写一个接受具有两个参数的表达式的方法,一个“真实”参数和一个您想用另一个表达式的值替换的某个值的参数。然后,您可以拥有一个解析为该值的表达式,并将参数的所有实例替换为第二个表达式:

public static Expression<Func<TSource, TResult>> BuildExpression
    <TSource, TOther, TResult>(
    Expression<Func<TSource, TOther, TResult>> function,
    Expression<Func<TOther>> innerExpression)
{
    var body = function.Body.Replace(function.Parameters[1], innerExpression.Body);
    return Expression.Lambda<Func<TSource, TResult>>(body, function.Parameters[0]);
}

您可以使用以下方法将一个表达式的所有实例替换为另一个:

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

然后您可以将其应用于您的案例,如下所示:

public static Expression<Func<int, int>> Add(Expression<Func<int>> expr)
{
    return BuildExpression((int i, int n) => i + n, expr);
}

【讨论】:

  • 不错。出于某种原因,我想到您需要覆盖 ExpressionVisitor 的所有成员,但当然只需 .Visit() 即可。不过,我确信可以为BuildExpression 设计更通用的东西——它用表达式替换第二个参数并不是很清楚,并且将其缩放到多个替换值同样会很痛苦。
  • @JeroenMostert 更大程度地泛化它当然是可能的,但是需要做更多的工作,是的,所以为了简单起见,我将范围限制在解决这个问题所需的范围内,以便它仍然可以理解。
  • 我用ReplaceVisitor 的潜在替代方式更新了我的答案。我想我会继续寻找圣杯一段时间作为练习。 :-)
  • @JeroenMostert 一个完全通用的解决方案将是一两百行代码,如果您想要针对一般用途进行大量优化的东西,可能会更多,并且解释这样的解决方案会增加很多复杂性,远远超出了 SO 答案所能适应的范围。不过这当然是可能的。
【解决方案2】:

您不能将任意 Expression 实例与编译时表达式树混合。您可以做的是构造一个替换特定节点的新表达式树,因此您可以拥有i =&gt; i + marker,然后构造一个新树,其中marker 节点被您的运行时表达式替换。这需要写一个合适的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()()

可以通过观察编译器将捕获我们在闭包中插入的表达式来解释,无论它来自何处,因此方法调用的参数始终是对该闭包的引用,我们需要对其进行评估得到实际的表达。

这可以扩展到任意数量的替换表达式,并且显式键入最少。

【讨论】:

  • 我已经修改了这个答案,从使用 Servy 的 ReplaceVisitor 的不同方式转变为使用表达式本身作为占位符的新方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多