Thursday 22 August 2013

Delegates Caching

I'm not much sure how, but some days ago I came across this interesting question in StackOverflow, answered no more than by 2 of the C# Gods: Erick Lippert and Jon Skeet.

The method that backs the delegate for a given lambda is always the same. The method that backs the delegate for "the same" lambda that appears lexically twice is permitted to be the same, but in practice is not the same in our implementation. The delegate instance that is created for a given lambda might or might not always be the same, depending on how smart the compiler is about caching it.

A lambda expression which doesn't capture any variables is cached statically A lambda expression which only captures "this" could be captured on a per-instance basis, but isn't A lambda expression which captures a local variable can't be cached

So the C# compiler is smart enough to cache Delegate Instances when possible to avoid creating the same instance over an over. This comes to me as a really interesting revelation, as on occasion I've felt slightly uncomfortable when writing code involving many lambdas as it seemed to me like an "object explosion".

I've done some tests to verify the above claims.

The delegate returned below is not capturing anything (it's not a closure), so we can see caching at work!

public static Func<string, string> CreateFormatter()
{
return st => st.ToUpper();
} 
 ...
var func1 = CreateFormatter();
var func2 = CreateFormatter();
Console.WriteLine("simple delegate being cached? " + Object.ReferenceEquals(func1, func2)); //true

If we take a look at the generated IL, we can see the cryptically named field "CS$<>9__CachedAnonymousMethodDelegate1" used to cache the delegate:

On the contrary, if the code returns a closure, it should be obvious that caching can't take place, as we need different instances, each one with access to the corresponding captured values (the trapped values are properties in an instance of a support class that the compiler creates under the covers and that is pointed from the delegate's Target property).

public static Func<string, string> CreateFormatterClosure(string s)
{
 return st => s + (st.ToUpper()) + s;
}
func1 = CreateFormatterClosure("x");
func2 = CreateFormatterClosure("x");
Console.WriteLine("closure being cached? " + Object.ReferenceEquals(func1, func2)); //false

Notice that I'm using Object.ReferenceEquals rather than == to check for object identity because the == operator for delegates is overloaded to do a value comparison. From msdn:

Two delegates of the same type with the same targets, methods, and invocation lists are considered equal.

If we try similar code in JavaScript, we'll see that there's not any hidden compiler trick and no function caching is done, so each time you create a new function, a new function object is being created

function createFunction(){
 return function(){};
}
console.log(createFunction() == createFunction());//false

(function(){}) == (function(){}); //false

To avoid this, I remember having seen in some library code something like var emptyFunc = function(){}; in order to reuse that unique function wherever a "do nothing" function were needed.

Summing up, the C# compiler does a really great job again (as it does with Closures, Iterators (yield), dynamic, async... It's no wonder why it's taking longer than expected to the Roslyn guys to rewrite the Native compiler in C#

No comments:

Post a Comment