我有一个Expression<Func<TElement, TElement, bool>>
类型的表达式和一个TElement
类型的常量。我需要一个Expression<Func<TElement, bool>>
类型的表达式,其中一个参数替换为常量。换句话说,我需要将主体设置为以下方法:
public static Expression<Func<TElement, bool>> ReplaceParameter<TElement>
(
Expression<Func<TElement, TElement, bool>> inputExpression,
TElement element
)
{
...
}
如果我致电ReplaceParameter((i1, i2) => i1 > i2, 5)
,我希望结果是i => i > 5
。
我在想,它可能能够递归解构,然后重构输入表达式,并用一个常量表达式替换第二个参数的所有出现。由于存在多种不同的表达式,因此我不确定如何执行该操作。
答案 0 :(得分:4)
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);
}
请注意,这不会自动折叠纯常量表达式,即显示的代码将导致:
y => ((3 * 2) == (y + 1))
您可以,但是,如果您愿意,请尝试寻找仅以BinaryExpression
作为输入的ConstantExpression
,然后再次在Replacer
内直接评估节点
答案 1 :(得分:3)
您应该使用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);
}
}
}
然后
Expression<Func<Int32, Int32, Boolean>> f = (i1, i2) => i1 > i2;
Expression<Func<Int32, Boolean>> f2 = ReplaceParameterVisitor.ReplaceParameter(f, 5);
Boolean b = f2.Compile()(4);
答案 2 :(得分:0)
您可以像这样创建工厂ExpressionVisitor
,而不是使用Func
:
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);
}
为使其更加有用,您可以保存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);
}
这里实际上发生了什么?
当inputExpression
为(i1, i2) => i1 > i2
时,inner
将为i1 => i1 > i2
,而outer
/ factory
将为i2 => i1 => i1 > i2
。