Saturday, 15 August 2009

Using reflector to understand anonymous methods and captured variables

C# 2.0’s ability to magically hoist “captured variables” into classes is often misunderstood, and there are a lot of tricky edge cases (especially when combined with the notorious “foreach” glitch).

So; how can we understand what is going on? Usually, the first tool to look at is reflector; so lets try with a simple example:

int div;
do { Console.WriteLine("Find numbers divisible by..."); }
while (!int.TryParse(Console.ReadLine(), out div));

int[] data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17 };

var filtered = data.Where(num => num % div == 0);
foreach (var i in filtered) Console.WriteLine(i);

So what is really happening here? Load it into Red Gate’s .NET Reflector, and by default it doesn’t actually seem to help us much, ironically because it does such a good job of interpreting the code:

image

I’ll forgive it for interpreting “Where” as LINQ query syntax, since the two are 100% interchangeable. What we don’t want to have to do is look at the IL – it is, lets be honest, not fun – here’s a small fragment of it:

image

But if you look at the options in reflector:

image image

you can choose the level of magic it applies. Lets try scaling it back to .NET 2.0 (a trick I use often to investigate the lambda Expression API):

image

Here, it has got rid of the query syntax (and would remove lambdas, too), and now uses the anonymous method approach (“delegate” etc), but it hasn’t really changed much (since C# 2.0 still allows captured variables in anonymous methods).

As an aside, it is very curious that it has left “data.Where” (rather than “Enumerable.Where(data)”) – I have left feedback with Red Gate ;-p

So lets try .NET 1.0:

image

Now we’re talking! Here we can clearly see the class (<>c__DisplayClass1) and local variable / instance (CS$<>8__locals2) that is used to do the hoisting, the hoisted field (CS$<>8__locals2.div) and the anonymous method (CS$<>8__locals2.<Main>b__0) that provides the logic implementation. All hyperlinked up for easy navigation - to the compiler-generated class; this is invaluable for anything non-trivial:

[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
// Fields
public int div;

// Methods
public bool
b__0(int num)
{
return ((num % this.div) == 0);
}
}

Interestingly, we can also scale it back a level further to “None”; which shows compatible C# without candy like “foreach”. This is verbose for C#, but terse compared to the IL version; here’s the “foreach”:

image

The declaration isn’t in the above grab (it is at the top of the method), but this also highlights how the iteration variable (“i”) is declared outside of the loop, hence the “foreach”/”capture” issue.

Again, I find it quite fun that in this level of optimisation it doesn’t show “foreach”, but knows about extension methods; but I’m not complaining – it seems ungrateful to criticise such a minor glitch in such a wonderful (and free) tool. But for people using non-standard LINQ implementations (or just to be true to the optimisation level), it would be nice if it showed this appropriately.

So there you go; yet another way to use reflector.