Tuesday 21 February 2012

Closures and Local Variables

When reading about Java pseudoClosures in the form of Anonymous classes, something that seemed really odd to me is that the local variables or parameters to be trapped by the pseudoClosure had to be final.

As answered here the reason for this is that as those variables are turned by the compiler into local fields of the anonymous class (same as the C# compiler does with delegates and closures) it could seem wrong if we modified those trapped variables inside the closure but they kept its old value when used in the method where the closure was defined.

Well, the explanation makes sense, and is a problem that does not exist with JavaScript closures given how these are implemented there (all the Execution Context object thing...) but, how do things work in C# then?

We should have the same problem, in fact it's something that I had never thought about before, but this excellent article explains that it's different in C#, where we get the same behaviour (that is the correct one for a closure) as in JavaScript: if we modify the trapped variable inside the closure, other closures using that same variable, and the outer method where they were defined, will see that change. It's really surprising in terms of how the Compiler does that, so I had to write some code and open it up with ILSpy to see what's happening under the covers.

The thing is that C#'s compiler is astonishingly intelligent, and we can see how the code where the local variables is defined ends up using the corresponding field added to the generated Class used for containing the closure. Really neat stuff.

The highlighted code in the IL below:

corresponds to the last WriteLine in this C# code:

Person p1 = new Person(){Name = "Xana"};
  Console.WriteLine("TestReferenceType, p1.Name: " + p1.Name);
Action personClosure1 = () => {
   Console.WriteLine("inside personClosure1, p1.Name: " + p1.Name);
   p1.Name += " Llera";
   Console.WriteLine("inside personClosure1, p1.Name: " + p1.Name);
  };
  personClosure1();
  Console.WriteLine("TestReferenceType, p1.Name: " + p1.Name);

so as I said below, the compiler is turning the access to that local variable (in that IL it would be a ldloc.2 instruction) into an access to a field in the class supporting the closure (what we see in that IL as Person App/'<>c__DisplayClass7'::p1

)

You can find the whole code here

No comments:

Post a Comment