1

Given the following code line of code,

Expression<Action> expression = () => target.ToString();

is there a fast way to obtain the target object?

Below code works

public object GetExpressionTarget<T>(Expression<T> expression)
{
    MethodCallExpression methodCall = (MethodCallExpression) expression.Body;
    LambdaExpression theTarget = Expression.Lambda(methodCall.Object, null);
    Delegate compiled = theTarget.Compile();

    return compiled.DynamicInvoke();    }

but is very, very slow.


Is there a faster way to get the target of a method call expression?


Benchmarking my code (GetDelegate, DelegateCompile and DelegateDynamicInvoke) as well as @IvanStoev's code (GetFunc, FuncCompile and FuncInvoke) yields this result:

|                Method |           Mean |         Error |        StdDev |
|---------------------- |----------------|---------------|---------------|
|       DelegateCompile | 165,068.477 ns | 2,671.3001 ns | 2,498.7358 ns |
|           FuncCompile | 160,956.199 ns | 2,133.5343 ns | 1,995.7093 ns |
| DelegateDynamicInvoke |   1,148.191 ns |    11.7213 ns |    10.9642 ns |
|            FuncInvoke |       3.040 ns |     0.0264 ns |     0.0247 ns |

So, Invoke is actually quite faster than DynamicInvoke, but the bottleneck is actually the Compile call. Is there a way to get the target object without having to compile expressions?

Benchmark code:

public class Program
{
    private Delegate @delegate;
    private Func<object> func;

    private static Delegate GetDelegate(Expression<Action> expression)
    {
        MethodCallExpression methodCall = (MethodCallExpression) expression.Body;
        return Expression.Lambda(methodCall.Object, null).Compile();
    }

    private static Func<object> GetFunc(Expression<Action> expression)
    {
        MethodCallExpression methodCall = (MethodCallExpression) expression.Body;
        return Expression.Lambda<Func<object>>(methodCall.Object).Compile();
    }

    [GlobalSetup]
    public void Setup()
    {
        object o = new object();
        Expression<Action> expression = () => o.ToString();

        this.@delegate = Program.GetDelegate(expression);
        this.func = Program.GetFunc(expression);
    }

    [Benchmark]
    public void DelegateCompile()
    {
        object o = new object();
        Expression<Action> expression = () => o.ToString();

        Program.GetDelegate(expression);
    }

    [Benchmark]
    public void FuncCompile()
    {
        object o = new object();
        Expression<Action> expression = () => o.ToString();

        Program.GetFunc(expression);
    }

    [Benchmark]
    public void DelegateDynamicInvoke()
    {
        this.@delegate.DynamicInvoke();
    }

    [Benchmark]
    public void FuncInvoke()
    {
        this.func.Invoke();
    }

    public static void Main(string[] args)
    {
        BenchmarkRunner.Run<Program>();
    }
}
Thomas Flinkow
  • 3,956
  • 5
  • 23
  • 52
  • 1
    I think `Compile` is the slowest part, but how about `Expression.Lambda>(methodCall.Object).Compile().Invoke()` – Ivan Stoev Jan 09 '19 at 14:20
  • @IvanStoev thank you for your idea, but sadly this does not improve performance either. Benchmarks showed `187 us` for the code in my question, and `195 us` for your proposed code (which isn't much worse, but it's not what I hoped for). – Thomas Flinkow Jan 09 '19 at 14:28
  • 1
    Did you separate `Compile()` from `Invoke()` when measuring (like in the code in question)? Or you are measuring the whole method? At least you can see if there is a difference between `DynamicInvoke` and `Invoke`. I still believe the slowest part is the `Compile()` method, not the invocation (one or another way). – Ivan Stoev Jan 09 '19 at 14:37
  • 1
    @IvanStoev please see the edits. You're right that `Compile` actually is the slowest part. Also, your approach is quite faster than using `DynamicInvoke`, but as it uses `Compile`, it is still very slow. Thank you for your help nevertheless – Thomas Flinkow Jan 10 '19 at 10:10
  • It should say "the bounty-winning **answer** would optimally propose code that works (quite) faster than the code in the question" of course... – Thomas Flinkow Jan 11 '19 at 16:54

1 Answers1

6

The only way I can think of to avoid the time costly Compile operation is to evaluate the expression content recursively using reflection.

Doing this generically (handling all the cases) is a complicated task. Currently there are more than 80 ExpressionTypes, all of them with different semantics (well, some fall into categories with corresponding base classes). In order to handle them all, one should probably create custom ExpressionVisitor and implement evaluation engine (probably with some sort of an evaluation stack).

In other words, a lot of work/code.

However... If we limit the expressions to 2 types - ConstantExpression (constant value) and MemberExpression (field or property of a constant value), then there is a relatively easy solution. The method in question already contains assumption about the passed Expression<Action> and the sample expression target (which is a closure) falls into constant value field category.

The main work is done in a private recursive method as follows:

static object Evaluate(Expression expression)
{
    if (expression == null)
        return null;
    if (expression is ConstantExpression constExpression)
        return constExpression.Value;
    if (expression is MemberExpression memberExpression)
    {
        var target = Evaluate(memberExpression.Expression);
        if (memberExpression.Member is FieldInfo field)
            return field.GetValue(target);
        if (memberExpression.Member is PropertyInfo property)
            return property.GetValue(target);
    }
    throw new NotSupportedException();
}

and the method in question using it would be

public object GetExpressionTarget<T>(Expression<T> expression)
{
    var methodCall = (MethodCallExpression)expression.Body;
    return Evaluate(methodCall.Object);
}

I have no performance comparison results, but even though this is using reflection, it should be much faster than the Compile which uses reflection and dynamic IL code emit, not counting creation of a DynamicMethod and delegate for invoking it.

Ivan Stoev
  • 159,890
  • 9
  • 211
  • 258
  • 1
    Ivan, thank you so much for your answer. The code works great for my use case, and is much faster than the code in the question - `1,600 ns` compared to `160,000 ns`. Thank you not only for giving code that does the task, but also for explaining how to write code for all the expression types. You really deserve the bounty :) – Thomas Flinkow Jan 13 '19 at 09:43
  • i started writing this after seeing how Compile is the slowest thing ever and thenw as like, i bet someone already did this for me!. You should expand to include MethodCallExpression – Chris DaMour Sep 30 '20 at 20:16