Изменение имени параметра в LambdaExpression только для отображения

Скажем, у меня есть такое выражение:

Expression<Predicate<T>> exp

Если я назначу следующее выражение:

a => a.First() != 0

а затем я вызываю exp.ToString(). Я получу именно то, что я передал, это прекрасно, но, предположим, мы хотим изменить имя, которое мы используем для 'a', с чем-то другим, как мы можем это сделать? Замена строк не будет выполняться во всех случаях (она работает в приведенном выше примере, но что, если параметр был назван "i", например?) Возможно ли иметь только замену имени параметра, время выполнения, не влияя на семантическое выражение?

UPDATE @PhilKlein отлично работает, но требует фреймворка 4. Но если нам нужно настроить таргетинг на среду 3.5, мы можем использовать класс Matt Warren, просто изменив из защищенного на публичный метод Visit.

Ответ 1

Это быстро и грязно, но если вы используете .NET 4.0, вы можете создать следующее:

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

И затем используйте его следующим образом:

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

Ответ 2

Выражения неизменны, поэтому вы не можете их модифицировать, вам нужно будет построить новое дерево.

В .NET 4.0 есть класс, который может помочь вам значительно, см. ExpressionVisitor

Вы можете сделать:

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

а затем new Renamer().Rename(exp).ToString() должен придерживаться того, что вы ожидаете.

Ответ 3

Обычно я использую инструмент рефакторинга, такой как Jetbrains Resharper, для этого. Он имеет функцию "Refactor, Rename", которая позволяет вам делать именно это и знает разницу между заменой строки и переименованием переменной. Я не знаю такой возможности из самой Visual Studio. http://www.jetbrains.com/resharper/

Однако, если вы ссылаетесь на создание динамического выражения и хотите изменить параметр, вы можете использовать код, например следующий (скопированный из: С# List <string> to Lambda Expression с примером стартера: Refactor для обработки списка)

    // 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();

И чтобы изменить параметр с "x" на "y", мы могли бы сделать следующее:

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