Изменение имени параметра в 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;
}