16

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.

Felice Pollano
  • 32,832
  • 9
  • 75
  • 115
  • I don't understand why you want to output a string representing the expression where it doesn't exactly match the expression. Why do you want to do this? Why *don't* you want to refactor the code -- why does it have to happen at runtime? – Kirk Woll Dec 16 '11 at 22:52
  • @KirkWoll I'm using lambda expression as metadata. In the context the expression is generated the argumet can be different from the one I want to display to the user in another point in code. The whole little project is an argument validation library that does not use magic string to display errors. – Felice Pollano Dec 17 '11 at 10:05

3 Answers3

11

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
Phil Klein
  • 7,344
  • 3
  • 30
  • 33
  • But it is going to change all parameters to "b". If you want only "a" to be changed to "b", see my solution. – Krizz Dec 17 '11 at 15:56
8

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.

Krizz
  • 11,362
  • 1
  • 30
  • 43
  • you are right the other one rename all the parameters, but your proposal did not work out of the box for me: maybe because VisitParameter is not overriding ? – Felice Pollano Dec 19 '11 at 21:48
  • 1
    doesn't this make distinct parameters that just happen to have the same name? So the generated expression will .ToString() as intended, but not compile correctly, right? – Eamon Nerbonne Feb 01 '13 at 17:13
0

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;
    }
Community
  • 1
  • 1
JNadal
  • 408
  • 2
  • 8
  • Seems the way to go (+1) but the example creates a new expression froms cratch, per contrary mine is already on. – Felice Pollano Dec 16 '11 at 22:57
  • The code above works, then, however it doesn't really do what you specified -- converting the string back into an expression, and then changing the parameter name. There's a rather long discussion here on how there is no lambda expression parser from a string, mainly because there could be issues with (if I interpreted the discussion correctly) variable scoping conflicts: http://www.tech-archive.net/Archive/DotNet/microsoft.public.dotnet.languages.csharp/2007-05/msg04019.html If your source is not specifically a string, you could alter the parameters of your Expression object. – JNadal Dec 16 '11 at 22:57
  • I appended the answer with how to change the (only, in this case.. you may want to look at exp.Parameters.Count in case you're looking at an (x,y)=>blah(x,y) type of expression ) parameter name from one to another. – JNadal Dec 16 '11 at 23:02
  • Unfortunately `exp.Parameters[0].Name = "y"` will not work since Expression Trees are immutable. – Phil Klein Dec 16 '11 at 23:43