john spurlock 2005


more on c# 2.0 anonymous methods

How did they do that?

Reflector makes it pretty easy to figure out what the new C# compiler does behind the scenes to handle the new anonymous delegate syntax.

It's actually very similar to what Java does to support anonymous inner classes, but takes it a step further.

Java generates an inner nested class and copies any captured local variables or the parent class pointer into private instance variables (through a generated constructor). This allows them to stay alive after the original scope returns, but necessarily constrains what you can capture to immutable variables (ie marked as final). This only becomes a pain when you really want to reassign a local variable in the enclosing scope - like incrementing a captured integer counter. Of course there are ways around this limitation, usually involving wrapping the integer in a class wrapper or worse: final int[] holder = new int[]{value};

C# doesn't have the reassignment limitation, and has to be a bit more creative.

In the simplest case, it actually doesn't generate a nested class at all - if the anonymous method doesn't use any captured variables or outer class references. Instead it generates a static method to serve as the delegate target (and even caches the delegate reference after it's first use, super-optimized! Makes you wonder how expensive the delegate-object overhead actually is...)

When the anonymous method makes use of variables in the enclosing scope, it doesn't simply copy the variables to the nested class, it shares them as public fields on the generated class. What makes this interesting is that it will rewrite any references to the captured variable in the enclosing scope as well. So your unassuming method-local int i = 123; is actually setting a public field on a class instance the compiler just made for you.

This is actually kind of strange - now there is a conceptual break between the code you write and the IL that is generated. Up until now, your method-local variables became local variables in IL, and field accessors were IL field instructions, etc. No longer the case.

I'll take the tradeoff though, it's certainly much nicer to have direct access to captured variables. Still waiting on full-fledged anonymous classes though, which is quite frustrating.

(btw the C# generated classes are named like <>c__DisplayClass1, they are private, sealed, and have a CompilerGenerated attribute. Note that they are not marked with Serializable, so make sure any "container" in your object graph - like an event sink - that holds these kind of generated classes does not participate, or it will break your serialization.)