一般而言,简化表达式的方法是编译它并执行编译后的委托。但是,如果表达式中仍有参数表达式,因为您不知道参数的值(尚未确定),所以无法这样做。这意味着我们有两个基本步骤:首先确定在我们的树中哪些子表达式实际上包含某个参数,然后评估所有没有包含参数的子表达式。
因此,第一步是确定哪些表达式包含参数。为此,我们创建一个表达式访问器,该访问器具有指示其当前是否位于具有参数的子树中的字段,然后递归检查其子项,再检查自身,并组合结果,沿途将所有不包含参数的表达式添加到一个集合中。
private class ParameterlessExpressionSearcher : ExpressionVisitor
{
public HashSet<Expression> ParameterlessExpressions { get; } = new HashSet<Expression>();
private bool containsParameter = false;
public override Expression Visit(Expression node)
{
bool originalContainsParameter = containsParameter;
containsParameter = false;
base.Visit(node);
if (!containsParameter)
{
if (node?.NodeType == ExpressionType.Parameter)
containsParameter = true;
else
ParameterlessExpressions.Add(node);
}
containsParameter |= originalContainsParameter;
return node;
}
}
接下来,为了评估没有参数的子表达式,我们需要另一个访问者。这个访问者只需检查表达式是否在我们使用前一个访问者找到的表达式集合中,如果是,就将该表达式编译成无参数的委托并执行,否则它会检查其子节点以查看是否可以替换任何子表达式。
private class ParameterlessExpressionEvaluator : ExpressionVisitor
{
private HashSet<Expression> parameterlessExpressions;
public ParameterlessExpressionEvaluator(HashSet<Expression> parameterlessExpressions)
{
this.parameterlessExpressions = parameterlessExpressions;
}
public override Expression Visit(Expression node)
{
if (parameterlessExpressions.Contains(node))
return Evaluate(node);
else
return base.Visit(node);
}
private Expression Evaluate(Expression node)
{
if (node.NodeType == ExpressionType.Constant)
{
return node;
}
object value = Expression.Lambda(node).Compile().DynamicInvoke();
return Expression.Constant(value, node.Type);
}
}
现在我们只需要一个简单的方法,先执行第一个搜索器,然后执行第二个搜索器并返回结果,同时提供一个重载方法将结果转换为通用表达式:
public static class ExpressionExtensions
{
public static Expression Simplify(this Expression expression)
{
var searcher = new ParameterlessExpressionSearcher();
searcher.Visit(expression);
return new ParameterlessExpressionEvaluator(searcher.ParameterlessExpressions).Visit(expression);
}
public static Expression<T> Simplify<T>(this Expression<T> expression)
{
return (Expression<T>)Simplify((Expression)expression);
}
}
现在您可以写成:
Expression<Func<Products, bool>> e = x => x.Id == i;
e = e.Simplify();
Console.WriteLine(e);
它会打印:
"x => (x.Id == 9)"
或者,如果您只想评估一个特定的表达式,并且愿意更改首先写入表达式的方式以适应,这个答案展示了如何编写一种方法来指示哪些子表达式应该被评估,以及仅评估那些表达式。 如果您想要评估某些内容而不是其他内容,那将非常有用。