6

I have this method and parameter.

void SomeMethod(Expression<Func<Products, bool>> where)

I call this method like this;

int i = 9;
SomeMethod(x=>x.Id==i)

And I want it to produce this string;

"x=>x.Id==9"

If I just print out the above expression as it is it will give me this string:

"x => (x.Id == value(isTakibi.WepApp.Controllers.HomeController+<>c__DisplayClass4_0).i)"

but I need "x.Id == 9". I need to evaluate the value of the variable i so that the result will be "x.id==9".

Servy
  • 202,030
  • 26
  • 332
  • 449
  • 3
    Your question is too specific, making it unclear what you're attempting to do. Take a step back and explain a little more of your context. – Jonathan Wilson Dec 07 '18 at 20:19
  • i want to write method caching. So i will write to cache key this string linq query and my database table. So i must catch all linq parameters and make it string. – Recep Gündoğdu Dec 07 '18 at 20:31

4 Answers4

12

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.

Servy
  • 202,030
  • 26
  • 332
  • 449
1

.ToString() works for me:

void SomeMethod(Expression<Func<Product, bool>> where)
{
    Console.WriteLine(where.ToString());
}

When calling with

SomeMethod(x=>x.Id==9);

Outputs:

x => (x.Id == 9)

Alex
  • 37,502
  • 51
  • 204
  • 332
  • yes but i call with SomeMethod(x=>x.Id==i); so outputs : "x => (x.Id == i)". i need to see the value of i varible. because it is dynamic value. – Recep Gündoğdu Dec 07 '18 at 21:14
1

As others have noted, you can get some resemblance of the original expression by calling ToString() on it, but this only works for very simple implementations and won't work well with closures. The c# compiler does a lot of "magic" to make things like closures work in expressions, and the "<>DisplayClass" stuff you're seeing is the result of that. You'd need to implement a custom visitor to go through the expression and write out the c# (essentially a reverse compiler) to get back to the original.

It would probably look something like the following stub:

public sealed class ExpressionWriterVisitor : ExpressionVisitor
{
  private TextWriter _writer;

  public ExpressionWriterVisitor(TextWriter writer)
  {
    _writer = writer;
  }

  protected override Expression VisitParameter(ParameterExpression node)
  {
    _writer.Write(node.Name);
    return node;
  }

  protected override Expression VisitLambda<T>(Expression<T> node)
  {
    _writer.Write('(');
    _writer.Write(string.Join(',', node.Parameters.Select(param => param.Name)));
    _writer.Write(')');
    _writer.Write("=>");

    Visit(node.Body);

    return node;
  }

  protected override Expression VisitConditional(ConditionalExpression node)
  {
    Visit(node.Test);

    _writer.Write('?');

    Visit(node.IfTrue);

    _writer.Write(':');

    Visit(node.IfFalse);

    return node;
  }

  protected override Expression VisitBinary(BinaryExpression node)
  {
    Visit(node.Left);

    _writer.Write(GetOperator(node.NodeType));

    Visit(node.Right);

    return node;
  }

  protected override Expression VisitMember(MemberExpression node)
  {
    // Closures are represented as a constant object with fields representing each closed over value.
    // This gets and prints the value of that closure.

    if (node.Member is FieldInfo fieldInfo && node.Expression is ConstantExpression constExpr)
    {
      WriteConstantValue(fieldInfo.GetValue(constExpr.Value));
    }
    else
    {
      Visit(node.Expression);
      _writer.Write('.');
      _writer.Write(node.Member.Name);
    }
    return node;
  }

  protected override Expression VisitConstant(ConstantExpression node)
  {
    WriteConstantValue(node.Value);

    return node;
  }

  private void WriteConstantValue(object obj)
  {
    switch (obj)
    {
      case string str:
        _writer.Write('"');
        _writer.Write(str);
        _writer.Write('"');
        break;
      default:
        _writer.Write(obj);
        break;
    }
  }

  private static string GetOperator(ExpressionType type)
  {
    switch (type)
    {
      case ExpressionType.Equal:
        return "==";
      case ExpressionType.Not:
        return "!";
      case ExpressionType.NotEqual:
        return "!==";
      case ExpressionType.GreaterThan:
        return ">";
      case ExpressionType.GreaterThanOrEqual:
        return ">=";
      case ExpressionType.LessThan:
        return "<";
      case ExpressionType.LessThanOrEqual:
        return "<=";
      case ExpressionType.Or:
        return "|";
      case ExpressionType.OrElse:
        return "||";
      case ExpressionType.And:
        return "&";
      case ExpressionType.AndAlso:
        return "&&";
      case ExpressionType.Add:
        return "+";
      case ExpressionType.AddAssign:
        return "+=";
      case ExpressionType.Subtract:
        return "-";
      case ExpressionType.SubtractAssign:
        return "-=";
      default:
        return "???";
    }
  }
}

Note in VisitMember where it has some logic to extract the value out of a closure.

This will print out "(x)=>x.Id==9":

static void Main(string[] args)
{
  var i = 9;
  Expression<Func<Product, bool>> where = x => x.Id == i;
  new ExpressionWriterVisitor(Console.Out).Visit(where);
}
Eric Damtoft
  • 1,353
  • 7
  • 13
0

In your example, your code is doing the right thing. The problem is that the variable i is not declared const. The expression has to assume that the value of i might change before it is getting called. This code gives you the result you expect: const int i = 9;

Unfortunately, this approach probably won't work for method caching for the same reason. Your code also doesn't have a way to ensure that i doesn't change between the time the expression is declared and the time when it is evaluated.

You could try writing an ExpressionVisitor that tries to find closures like that and evaluate them, but I've never tried it myself.

Leo Bartkus
  • 1,925
  • 13
  • 17
  • but value of i is dynamic. so i can't write const. now i can get the value with this code, but i need to make it with dynamic varible name; `((dynamic)where).Body.Right.Expression.Value.i; // this code gives me "9"` – Recep Gündoğdu Dec 07 '18 at 21:25
  • Maybe you could make an ExpressionVisitor that can detect this case, evaluate it, and replace it with a const expression node. – Leo Bartkus Dec 07 '18 at 21:26