Changing parameter name in a LambdaExpression just

2019-03-18 12:17发布

问题:

Let's say I have an expression like this:

Expression<Predicate<T>> exp

If I assign the following expression:

a => a.First() != 0

and then I call exp.ToString() I will obtain exactly the expression I passed, that is perfectly good, but, suppose we want to change the name we use for 'a' with something else, how can we do ? String replacement would not do in all the case ( it works in the example above,but what if the parameter was called 'i' for example ?) Is it possible to have just the parameter name replacement, run time, without affecting the expression semantic ?

UPDATE The @PhilKlein works perfectly, but requires framework 4. But if we need to target the framework 3.5 we can use an ExpressionVisitor class from Matt Warren, by just modifing from protected to public the Visit method.

回答1:

It's quick and dirty, but assuming you're using .NET 4.0 you could create the following:

public class PredicateRewriter
{
    public static Expression<Predicate<T>> Rewrite<T>(Expression<Predicate<T>> exp, string newParamName)
    {
        var param = Expression.Parameter(exp.Parameters[0].Type, newParamName);
        var newExpression = new PredicateRewriterVisitor(param).Visit(exp);

        return (Expression<Predicate<T>>) newExpression;
    }

    private class PredicateRewriterVisitor : ExpressionVisitor
    {
        private readonly ParameterExpression _parameterExpression;

        public PredicateRewriterVisitor(ParameterExpression parameterExpression)
        {
            _parameterExpression = parameterExpression;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            return _parameterExpression;
        }
    }
}

And then use it as follows:

var newExp = PredicateRewriter.Rewrite(exp, "b");
newExp.ToString(); // returns "b => (b.First() == 0)" in your case


回答2:

Expressions are immutable so, therefore, you cannot modify them, you would need to construct new tree.

In .NET 4.0, there is a class which can help you significantly, see ExpressionVisitor

You can do:

public class Renamer : ExpressionVisitor
{
    public Expression Rename(Expression expression)
    {
        return Visit(expression);
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node.Name == "a")
            return Expression.Parameter(node.Type, "something_else");
        else
            return node;
    }
}

and then, new Renamer().Rename(exp).ToString() should hold what you expect.



回答3:

Typically I would use a refactoring tool, such as Jetbrains Resharper to do so. It has a feature "Refactor, Rename" which lets you do just that, and knows the difference between a string replace and a variable rename. I know of no such feature from within Visual Studio itself. http://www.jetbrains.com/resharper/

If you are referring to building a dynamic expression, however, and want to change the parameter, you can use code such as the following (copied from: c# List<string> to Lambda Expression with starter example: Refactor to handle the List)

    // Create a parameter which passes the object
    ParameterExpression param = Expression.Parameter(typeof(E), "x"); //x replaces a=>

    // Create body of lambda expression
    Expression body = Expression.PropertyOrField(param, fieldname);

    // Create lambda function
    Expression<Func<E, string>> exp = Expression.Lambda<Func<E, string>>(body, param);

    // Compile it so we can use it
    Func<E, string> orderFunc = exp.Compile();

And to change the parameter from "x" to "y", we could do the following:

    var newExpression = ReplaceFirstParameterName(exp, "y");

    private Expression<Func<E, string>>(Expression<Func<E,string>> exp, string newParamName)
    {
       var cloneParam = Expression.Parameter(exp.Parameters[0].Type, newParamName);
       var body = exp.Body;
       var newExp = Expression.Lambda<Func<string, string>>(body, cloneParam);
       return newExp;
    }


标签: c# lambda