Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using an Expression in a compiler-generated expression tree

Tags:

c#

linq

I know I can create expression trees using:

  1. Factory methods.

  2. Compiler conversion of a lambda expression to an Expression.

For complicated expression trees I would prefer 2 because it is more concise.

Is it possible to refer to already constructed Expressions using this way?

using System;
using System.Linq.Expressions;

public class Test
{
    public static Expression<Func<int, int>> Add(Expression expr)
    {
#if false
        // works
        ParameterExpression i = Expression.Parameter(typeof(int));
        return Expression.Lambda<Func<int, int>>(Expression.Add(i, expr), i);
#else
        // compiler error, can I pass expr here somehow?
        return i => i + expr;
#endif
    }

    public static void Main()
    {
        Func<int, int> f = Add(Expression.Constant(42)).Compile();
        Console.WriteLine(f(1));
    }
}
like image 952
0xF Avatar asked Nov 18 '14 16:11

0xF


2 Answers

There isn't anything out of the box, but you can build a tool yourself to provide this functionality.

You can write a method that accepts an expression that has two parameters, one "real" parameter and one parameter of some value that you want to substitute with the value of another expression. You can then have an expression that resolves to that value and replace all instances of the parameter with the second expression:

public static Expression<Func<TSource, TResult>> BuildExpression
    <TSource, TOther, TResult>(
    Expression<Func<TSource, TOther, TResult>> function,
    Expression<Func<TOther>> innerExpression)
{
    var body = function.Body.Replace(function.Parameters[1], innerExpression.Body);
    return Expression.Lambda<Func<TSource, TResult>>(body, function.Parameters[0]);
}

You can use the following method to replace all instances of one expression with another:

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

You can then apply it to your case as follows:

public static Expression<Func<int, int>> Add(Expression<Func<int>> expr)
{
    return BuildExpression((int i, int n) => i + n, expr);
}
like image 132
Servy Avatar answered Oct 28 '22 02:10

Servy


You can't mix arbitrary Expression instances with compile-time expression trees. What you can do is construct a new expression tree with specific nodes replaced, so you can have i => i + marker and then construct a new tree with the marker node replaced by your runtime expression. This requires writing an appropriate ExpressionVisitor:

public static class ExpressionExtensions {
  public static T AsPlaceholder<T>(this Expression expression) {
    throw new InvalidOperationException(
      "Expression contains placeholders."
    );
  }

  public static Expression FillPlaceholders(this Expression expression) {
    return new PlaceholderExpressionVisitor().Visit(expression);
  }
}

class PlaceholderExpressionVisitor : ExpressionVisitor {
  protected override Expression VisitMethodCall(MethodCallExpression node) {
    if (
      node.Method.DeclaringType == typeof(ExpressionExtensions) && 
      node.Method.Name == "AsPlaceholder"  // in C# 6, we would use nameof()
    ) {
      return Expression.Lambda<Func<Expression>>(node.Arguments[0]).Compile()();
    } else {
      return base.VisitMethodCall(node);
    }
  }
}

Add now becomes:

public static Expression<Func<int, int>> Add(Expression expr) {
    Expression<Func<int, int>> add = i => i + expr.AsPlaceholder<int>();
    return (Expression<Func<int, int>>) add.FillPlaceholders();
}

The slightly cryptic expression

Expression.Lambda<Func<Expression>>(node.Arguments[0]).Compile()()

can be explained by observing that the compiler will capture the expression we're inserting in a closure, regardless of where it came from, so the argument of the method call is always a reference to this closure that we need to evaluate to get at the actual expression.

This scales to an arbitrary number of replacement expressions with a minimum of explicit typing.

like image 28
Jeroen Mostert Avatar answered Oct 28 '22 01:10

Jeroen Mostert