7
\$\begingroup\$

This is regards to one of my answers: Linq Expression Calling Combines. The question wasn't very well received, but it proved to be an interesting task that I enjoyed working on.

I ended up with the following:

public static class ExpressionHelpers
{
    public static TExpressionType InlineInvokes<TExpressionType>(this TExpressionType expression)
        where TExpressionType : Expression
    {
        return (TExpressionType)new InvokeInliner().Inline(expression);
    }

    public static Expression InlineInvokes(this InvocationExpression expression)
    {
        return new InvokeInliner().Inline(expression);
    }

    public class InvokeInliner : ExpressionVisitor
    {
        private Stack<Dictionary<ParameterExpression, Expression>> _context = new Stack<Dictionary<ParameterExpression, Expression>>();
        public Expression Inline(Expression expression)
        {
            return Visit(expression);
        }

        protected override Expression VisitInvocation(InvocationExpression e)
        {
            var callingLambda = e.Expression as LambdaExpression;
            if (callingLambda == null) //Fix as per comment
                return base.VisitInvocation(e);
            var currentMapping = new Dictionary<ParameterExpression, Expression>();
            for (var i = 0; i < e.Arguments.Count; i++)
            {
                var argument = Visit(e.Arguments[i]);
                var parameter = callingLambda.Parameters[i];
                if (parameter != argument)
                    currentMapping.Add(parameter, argument);
            }
            _context.Push(currentMapping);
            var result = Visit(callingLambda.Body);
            _context.Pop();
            return result;
        }

        protected override Expression VisitParameter(ParameterExpression e)
        {
            if (_context.Count > 0)
            {
                var currentMapping = _context.Peek();
                if (currentMapping.ContainsKey(e))
                    return currentMapping[e];
            }
            return e;
        }
    }
}

At its core, it simply in-lines Invoke() calls in the expression tree, replacing them with the called expression's code verbatim.

Examples:

INPUT:
Invoke(i => (i + 1), 3)

OUTPUT:
(3 + 1)  

INPUT: 
i => Invoke((i, j) => (i * j), Invoke(i => (i + 1), i), Invoke(i => (i + 2), i))

OUTPUT:
i => ((i + 1) * (i + 2))

INPUT:  
b => Invoke((d, e) => (d * e), Invoke(b => (50 + Invoke(z => (25 + Invoke(h => (h * 8), z)), b)), b), Invoke(c => (c + 2), b))  

OUTPUT:   
b => ((50 + (25 + (b * 8))) * (b + 2))  

Example usage:

Expression<Func<int, int>> f1 = i => i + 1;
Expression<Func<int, int>> f2 = i => i + 2;
Expression<Func<int, int, int>> f3 = (i, j) => i * j;

var p = Expression.Parameter(typeof(int), "i");
var r = Expression
    .Invoke(f3, new[] { 
        Expression.Invoke(f1, p), 
        Expression.Invoke(f2, p) }) 
    .InlineInvokes();

Expression<Func<int, int>> lam = Expression.Lambda<Func<int, int>>(r, p);

The only 'issue' I can come up with is that if one were to modify f1 after InlineInvokes() was called, they might be surprised if they were expecting f1 to be captured and lazily executed, but in my opinion, this is probably to be expected (any other opinions?).

In summary, my questions are:

  1. Are there any pitfalls in in-lining invocations (that is, is my idea fundamentally flawed, and cannot be solved 'properly')?

  2. Are there any issues with the code I've written itself?

\$\endgroup\$
  • \$\begingroup\$ After codereview.SE / rubberduck debugging, I have found a case where it does fail: Expression.Invoke(Expression.Constant(new Func<int>(() => 5))).InlineInvokes() - Are there any others? \$\endgroup\$ – Rob Jan 12 '16 at 7:49
  • \$\begingroup\$ What are the benefits of doing this? Why would I need it? Can you name a few examples where this is useful and how to apply it? \$\endgroup\$ – t3chb0t Nov 14 '16 at 7:34
  • \$\begingroup\$ @t3chb0t I suppose the most common reason would be with Entity Framework, as it does not allow Expression.Invoke in the expression tree. Personally, I have used the above code when writing an expression tree parser and encountering an Invoke expression - our output doesn't care whether or not the expression was invoked, so we simply inline the invokes. As for having to use Expression.Invoke - it's useful if you want to apply a delegate to an existing IQueryable<T> \$\endgroup\$ – Rob Nov 14 '16 at 8:50
2
\$\begingroup\$

I ended up using this code a few times, and recently was thinking through a scenario where the above will fail:

var iParam = Expression.Parameter(typeof(int));
var lParam = Expression.Parameter(typeof(int));

var original = Expression.Invoke(
    Expression.Lambda(
        Expression.Invoke(
                Expression.Lambda(
                    Expression.Add(iParam, lParam),
                    iParam),
                Expression.Constant(1)
        ),
        lParam),
    Expression.Constant(2)
);
var transformed = original.InlineInvokes();

The original can be visualized as:

Invoke(Param_0 => Invoke(Param_1 => (Param_1 + Param_0), 1), 2)

Here, the inner invocation is using a parameter from its parent. Since we're using a stack to represent the mappings between parameters, we're not taking into consideration Param_0. Here's the current output:

(1 + Param_0)

Here's the fixed code:

protected override Expression VisitInvocation(InvocationExpression e)
{
    var callingLambda = e.Expression as LambdaExpression;
    if (callingLambda == null)
        return base.VisitInvocation(e);
    var currentMapping = new Dictionary<ParameterExpression, Expression>();
    for (var i = 0; i < e.Arguments.Count; i++)
    {
        var argument = Visit(e.Arguments[i]);
        var parameter = callingLambda.Parameters[i];
        if (parameter != argument)
            currentMapping.Add(parameter, argument);
    }
    if (_context.Count > 0)
    {
        var existingContext = _context.Peek();
        foreach (var kvp in existingContext)
        {
            if (!currentMapping.ContainsKey(kvp.Key))
                currentMapping[kvp.Key] = kvp.Value;
        }
    }

    _context.Push(currentMapping);
    var result = Visit(callingLambda.Body);
    _context.Pop();
    return result;
}

Note that we only propagate the parameters if they don't already exist. This is required in situations as follows:

var iParam = Expression.Parameter(typeof(int));
var lParam = Expression.Parameter(typeof(int));

var original = Expression.Invoke(
    Expression.Lambda(
        Expression.Invoke(
                Expression.Lambda(
                    Expression.Add(iParam, lParam),
                    iParam, lParam),
                Expression.Constant(1), Expression.Constant(3)
        ),
        lParam),
    Expression.Constant(2)
);

Invoke(Param_0 => Invoke((Param_1, Param_0) => (Param_1 + Param_0), 1, 3), 2)

Here, the inner invoke is providing a value for Param_0 which takes precedence over what the outer invoke is providing. If we compile that expression (without inlining) - we see the result is 4.

Thus, we must ensure that we only add parameters from our parent context if we haven't already been provided them.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.