用常量替换表达式中的参数

时间:2019-05-28 07:52:35

标签: c# .net reflection expression

我有一个Expression<Func<TElement, TElement, bool>>类型的表达式和一个TElement类型的常量。我需要一个Expression<Func<TElement, bool>>类型的表达式,其中一个参数替换为常量。换句话说,我需要将主体设置为以下方法:

public static Expression<Func<TElement, bool>> ReplaceParameter<TElement>
(
    Expression<Func<TElement, TElement, bool>> inputExpression,
    TElement element
)
{
    ...
}

如果我致电ReplaceParameter((i1, i2) => i1 > i2, 5),我希望结果是i => i > 5

我在想,它可能能够递归解构,然后重构输入表达式,并用一个常量表达式替换第二个参数的所有出现。由于存在多种不同的表达式,因此我不确定如何执行该操作。

3 个答案:

答案 0 :(得分:4)

ExpressionVisitor是你的朋友:

static void Main()
{
    Expression<Func<int, int, bool>> before = (x, y) => x * 2 == y + 1;
    var after = ReplaceParameter(before, 3);
    Console.WriteLine(after);
}
public static Expression<Func<TElement, bool>> ReplaceParameter<TElement>
(
    Expression<Func<TElement, TElement, bool>> inputExpression,
    TElement element
)
{
    var replacer = new Replacer(inputExpression.Parameters[0],
        Expression.Constant(element, typeof(TElement)));
    var body = replacer.Visit(inputExpression.Body);
    return Expression.Lambda<Func<TElement, bool>>(body,
        inputExpression.Parameters[1]);
}
class Replacer : ExpressionVisitor
{
    private readonly Expression _from, _to;
    public Replacer(Expression from, Expression to)
    {
        _from = from;
        _to = to;
    }
    public override Expression Visit(Expression node)
        => node == _from ? _to : base.Visit(node);
}

请注意,这不会自动折叠纯常量表达式,即显示的代码将导致:

y => ((3 * 2) == (y + 1))

可以,但是,如果您愿意,请尝试寻找仅以BinaryExpression作为输入的ConstantExpression,然后再次在Replacer内直接评估节点

答案 1 :(得分:3)

您应该使用ExpressionVisitor,这将有助于您访问表达式并替换表达式的一部分。

public class ReplaceParameterVisitor : ExpressionVisitor
{
    public static Expression<Func<TElement, bool>> ReplaceParameter<TElement>(
        Expression<Func<TElement, TElement, bool>> inputExpression, 
        TElement element)
    {

        Expression body = inputExpression.Body;
        ReplaceParameterVisitor visitor = 
            new ReplaceParameterVisitor(inputExpression.Parameters[1], 
                                        Expression.Constant(element, typeof(TElement)));
        Expression newBody = visitor.Visit(body);

        Expression<Func<TElement, bool>> newExpression = 
            Expression.Lambda<Func<TElement, Boolean>>(
                newBody, 
                new ParameterExpression[] { inputExpression.Parameters[0] });
        return newExpression;
    }

    private ReplaceParameterVisitor(
        ParameterExpression param, 
        ConstantExpression constant)
    {
        this._param = param;
        this._constant = constant;
    }

    private readonly ParameterExpression _param;
    private readonly ConstantExpression _constant;


    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node == this._param)
        {
            return this._constant;
        }
        else
        {
            return base.VisitParameter(node);
        }
    }
}

然后

Expression<Func<Int32, Int32, Boolean>> f = (i1, i2) => i1 > i2;
Expression<Func<Int32, Boolean>> f2 = ReplaceParameterVisitor.ReplaceParameter(f, 5);
Boolean b = f2.Compile()(4);

答案 2 :(得分:0)

您可以像这样创建工厂ExpressionVisitor,而不是使用Func

public static Expression<Func<TElement, bool>> ReplaceParameter<TElement>
(
    Expression<Func<TElement, TElement, bool>> inputExpression,
    TElement element
)
{
    var inner = Expression.Lambda<Func<TElement, bool>>
    (
        inputExpression.Body,
        inputExpression.Parameters[1]
    );
    var outer = Expression.Lambda<Func<TElement, Expression<Func<TElement, bool>>>>
    (
        inner,
        inputExpression.Parameters[0]
    );
    var factory = outer.Compile();
    return factory(element);
}

为使其更加有用,您可以保存factory并在每次要替换参数时调用它:

public static Func<TElement, Expression<Func<TElement, bool>>> CreateFactory<TElement>
(
    Expression<Func<TElement, TElement, bool>> inputExpression
)
{
    var inner = Expression.Lambda<Func<TElement, bool>>
    (
        inputExpression.Body,
        inputExpression.Parameters[1]
    );
    var outer = Expression.Lambda<Func<TElement, Expression<Func<TElement, bool>>>>
    (
        inner,
        inputExpression.Parameters[0]
    );
    return outer.Compile();
}

public static void Test()
{
    var factory = CreateFactory<int>((i1, i2) => i1 > i2);
    var greater5 = factory(5);
    var greater2 = factory(2);
}

这里实际上发生了什么?
inputExpression(i1, i2) => i1 > i2时,inner将为i1 => i1 > i2,而outer / factory将为i2 => i1 => i1 > i2