Reputation: 9467
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
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.
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
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