简体   繁体   English

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

[英]Replace a parameter in an expression with a constant

I have an expression of type Expression<Func<TElement, TElement, bool>> and a constant of type TElement . 我有一个Expression<Func<TElement, TElement, bool>>类型的Expression<Func<TElement, TElement, bool>>和一个TElement类型的常量。 I need an expression of type Expression<Func<TElement, bool>> with one of the parameters replaced by the constant. 我需要一个Expression<Func<TElement, bool>>类型的Expression<Func<TElement, bool>> ,其中一个参数被常量替换。 In other words, I need the body to the following method: 换句话说,我需要身体以下方法:

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

If I call ReplaceParameter((i1, i2) => i1 > i2, 5) , I expect the result to be i => i > 5 . 如果我调用ReplaceParameter((i1, i2) => i1 > i2, 5) ,我希望结果为i => i > 5

I was thinking, it might be able to recursively deconstruct and then reconstruct the input expression and replace all occurrences of the second parameter with a constant expression. 我在想,它可能能够递归地解构,然后重构输入表达式,并用常量表达式替换所有出现的第二个参数。 Since there are so many different kind of expressions, I'm unsure on how to do that, though. 由于有很多不同类型的表达,我不确定如何做到这一点。

ExpressionVisitor is your friend: 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);
}

Note that this does not automatically collapse pure constant expressions, ie the code shown results in: 请注意,这不会自动折叠纯常量表达式,即显示的代码导致:

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

You could however, if you wanted, try looking for BinaryExpression that only has ConstantExpression as inputs, and evaluate the node directly, again inside Replacer . 可以但是,如果你想,尝试寻找BinaryExpression仅具有ConstantExpression内作为输入,并直接评价节点,再次Replacer

You should use an ExpressionVisitor which will helps you visit the expression and replace part of the expression. 您应该使用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);
        }
    }
}

Then 然后

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

Instead of using the ExpressionVisitor you could create a factory Func like this: 您可以像这样创建工厂Func而不是使用ExpressionVisitor

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);
}

To make it even more useful, you could save factory and call it every time you want to replace a parameter: 为了使它更有用,您可以保存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);
}

What is actually happening here? 这里到底发生了什么?
When inputExpression is (i1, i2) => i1 > i2 then inner would be i1 => i1 > i2 and outer / factory would be i2 => i1 => i1 > i2 . inputExpression(i1, i2) => i1 > i2 inner将是i1 => i1 > i2outer / factory将是i2 => i1 => i1 > i2

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM