This is part 4 in a series about state and function pointers; part 1 is here.
Last time, we saw that it is possible to pass local state with a delegate in C#. However, it involves lots of repetitive single-use classes, leading to ugly code.
To alleviate this tedious task, C# 2 supports anonymous methods, which allow you to embed a function inside another function. This makes my standard example much simpler:
//C# 2.0 int x = 2; int[] numbers = { 1, 2, 3, 4 }; int[] hugeNumbers = Array.FindAll( numbers, delegate(int n) { return n > x; } ); //C# 3.0 int x = 2; int[] numbers = { 1, 2, 3, 4 }; IEnumerable<int> hugeNumbers = numbers.Where(n => n > x);
Clearly, this is much simpler than the C# 1.0 version from last time. However, anonymous methods and lambda expressions are compile-time features; the CLR itself is not aware of them. How does this code work? How can an anonymous method use a local variable from its parent scope?
This is an example of a closure – a function bundled together with external variables that the function uses. The C# compiler handles this the same way that I did manually last time in C# 1: it generates a class to hold the function and the variables that it uses, then creates a delegate from the member function in the class. Thus, the local state is passed as the delegate’s this
parameter.
To see how the C# compiler implements closures, I’ll use ILSpy to decompile the more-familiar C# 3 version: (I simplified the compiler-generated names for readability)
[CompilerGenerated] private sealed class ClosureClass { public int x; public bool Lambda(int n) { return n > this.x; } } private static void Main() { ClosureClass closure = new ClosureClass(); closure.x = 2; int[] numbers = { 1, 2, 3, 4 }; IEnumerable<int> hugeNumbers = numbers.Where(closure.Lambda); }
The ClosureClass
(which was actually named <>c__DisplayClass1
) is equivalent to the GreaterThan
class from my previous example. It holds the local variables used in the lambda expression. Note that this class replaces the variables – in the original method, instead a local variable named x
, the compiler uses the public x
field from the ClosureClass
. This means that any changes to the variable affect the lambda expression as well.
The lambda expression is compiled into the Lambda
method (which was originally named <Main>b__0
). It uses the same field to access the local variable, sharing state between the original outer function and its lambda expression.