The way to simplify an expression in general is to compile it and execute the compiled delegate. Now you can't do that for any expression that still has any parameter expressions in it, because you don't know what the parameter's value will be (yet). This means we have two fundamental steps, first, determine which of the sub-expressions in our tree actually contain a parameter somewhere within that sub-tree, then evaluate all of the ones that don't.
So the first step is to determine which expressions contain a parameter within them. To do that we create an expression visitor that has a field indicating whether it's currently inside of a sub-tree with a parameter which then recursively checks its children, then checks itself, and then combines the results, adding all parameterless expressions into a collection while along the way.
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;
}
}
Next to evaluate the sub-expressions that have no parameter we need another visitor. This one just has to check if the expression is in the set of expressions we found with the previous visitor, and if so, compiles that expression into a parameterless delegate and executes it, otherwise it'll check its children to see if any of them can be replaced.
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);
}
}
Now we just need a simple method to first execute the first searcher, then the second, and return the results, and provide an overload that casts the result to a generic expression:
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
}
Now you can write:
Expression<Func<Products, bool>> e = x => x.Id == i;
e = e.Simplify();
Console.WriteLine(e);
And it'll print:
"x => (x.Id == 9)"
Alternatively, if you just want to evaluate one particular expression, and you're willing to change how you write the expression in the first place to accommodate, this answer shows how you can write a method to indicate what sub-expressions should be evaluated, and to evaluate just those expressions. That would be useful if you want to evaluate some things and not others.