CSharpie
CSharpie

Reputation: 9467

What is captured in Expression.Compile

Lets assume I got a class of Type Foo with a Property Bar.

And i got the following Method:

public static void DumpValue<T>(Expression<Func<T>> expr)
{
     MemberExpression memberExpression = expression.Body as MemberExpression;
     Debug.WriteLine("{0} => {1}", memberExpression.Member.Name, expr.Compile()());
}

And it is used like this:

Foo a = new Foo{Bar ="Hello"};
Foo b = new Foo{Bar ="World"};
DumpValue(() => a.Test);
DumpValue(() => b.Test);

Which gives the output:

Bar => Hello
Bar => World

My Question here concerns the consecutive Compile calls. Is it smart enough to rework the Func<T> Into something like Func<Foo,T> (internally) so the instance is removed so it can be used for any instance of Foo and only the resulting Delegate is instancespecific? Or does it indeed Compile it completely for each instance?

If the latter, do i need to worry about polluting Memory with tons of compiled functions, I cannot see any impact from my tests.

I know this can be avoided by rewriting the DumpValue, but i want to know what happens behind the Scenes. This is just an example here to illustrate.

I dug my way through the Source but couldnt find any clue.

Rephrasing this Question: Does the Compiler optimize the instance out and caches some info here and only bakes the instance into the final delegate, or does he go "all the way all the time"?

Upvotes: 2

Views: 561

Answers (2)

Mat&#237;as Fidemraizer
Mat&#237;as Fidemraizer

Reputation: 64931

My Question here concerns the Compile. Is it smart enough to rework the Func Into something like Func so the instance is removed so it can be used for any instance of Foo ? Or does it indeed Compile it for each instance?

When you compile an expression you get a delegate. If you want to use an arbitrary instance when you call the compiled expression, then you don't need a Expression<Func<T>> but Expression<Func<T, S>>.

Otherwise, you'll need to build an expression tree from the scratch to don't use captured references within the expression body.

About the caching thing...

I've modified your code to check that even when you compile twice the same expression tree, you get different delegate instances:

using System;
using System.Linq.Expressions;

public class Program
{
    public static Delegate DumpValue<T>(Expression<Func<T>> expr)
    {
        MemberExpression memberExpression = expr.Body as MemberExpression;

        return expr.Compile();
    }

    public static void Main()
    {
        string a = "foo";
        string b = "bar";

        var del1 = DumpValue(() => a);
        var del2 = DumpValue(() => b);

        // FALSE
        Console.WriteLine(Object.ReferenceEquals(del1, del2));
    }
}

In short, then, there's no caching and as my answer has stated from the start, I doubt that such feature would be easy to implement, because it might be very use cases and edge cases where generalizing expressions might be a time-consuming task. Probably in most cases many delegate instances are better than implementing a caching algorithm (and even expression tree transformations prior to get them compiled...).

Upvotes: 2

xanatos
xanatos

Reputation: 111850

I'll say that there is no caching anywhere of anything.

Expression<TDelegate>.Compile() calls LambdaCompiler.Compile(). From there a new instance of LambdaCompiler is created, that initializes the _method field (with a var method = new DynamicMethod(lambda.Name ?? "lambda_method", lambda.ReturnType, parameterTypes, true);) that will be then used to create first the method and then the delegate. Note that _method is readonly, so no one can change it, and is directly used by LambdaCompiler.CreateDelegate() to create the delegate. No caching anywhere.

Upvotes: 2

Related Questions