Чтобы упростить выражение в целом, нужно скомпилировать его и выполнить скомпилированный делегат.Теперь вы не можете сделать это для любого выражения, в котором все еще есть выражения параметров, потому что вы еще не знаете, каким будет значение параметра (пока).Это означает, что у нас есть два фундаментальных шага: во-первых, определить, какие из подвыражений в нашем дереве действительно содержат параметр где-то в этом поддереве, а затем оценить все те, которые не имеют.
Так чтоПервый шаг - определить, какие выражения содержат параметр внутри них.Для этого мы создаем посетителя выражения, в котором есть поле, указывающее, находится ли оно в данный момент в поддереве, с параметром, который затем рекурсивно проверяет своих потомков, затем проверяет себя и затем объединяет результаты, добавляя все выражения без параметров в коллекцию, в то время какпо пути.
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);
}
//all previously shown code goes here
}
Теперь вы можете написать:
Expression<Func<Products, bool>> e = x => x.Id == i;
e = e.Simplify();
Console.WriteLine(e);
И вы получите:
"x => (x.Id == 9)"
В качестве альтернативы, если вы просто хотите оценить одно конкретное выражение, ивы готовы изменить способ написания выражения в первую очередь для приспособления, этот ответ показывает, как вы можете написать метод для указания того, какие подвыражения следует оценивать, и для оценки только этих выражений.Это было бы полезно, если вы хотите оценить некоторые вещи, а не другие.