You Should Know C# Closures
22 September 2014

Lambda-calculus defined concept of closures in 1960s, but it was first time fully implemented in 1975 as a Scheme language feature. First time I heard of closure was at university, but didn’t think of it as a big deal until started to (more carefully) read JavaScript books. Than my closure attention shifted toward C#.

Outer and Captured Variables

Let’s define terminology thru example. In following examples I’ll use lambda notation, although same works for just plain old anonymous delegates.

static void CapturingTest(bool flag = true)
{
string outer = "Outer";
string outerAndCaptured = "OuterAndCaptured";
if(flag)
{
string local = "Local";
Console.WriteLine(local);
}
Action anonymousMethod = () =>
{
string anonymousLocal = "AnonymousLocal";
Console.WriteLine(outerAndCaptured + " " + anonymousLocal);
};
anonymousMethod();
}

Output from example above is:

Local
OuterAndCaptured  AnonymousLocal

Idea of closure is that function (in our case anonymous method) is able to interact with environment within it was created. Note that we didn’t pass any parameters to anonymous method and still we are able to use outerAndCaptured variable !

From example above we have two outer variables. Outer variable is variable defined in scope where scope includes an anonymous method. I think of outer as variable that could be captured by anonymous method. There goes another definition: Captured variable is an outer variable that is later used in anonymous method defined within the same scope. On the other side, inside if block we defined another local variable. This variable is regular local variable because there are no anonymous methods within its scope. Same goes for anonymousLocal variable.

Don’t get fooled, this is more useful that it looks like!

Capturing Is Not Copying

First we must understand that captured variable is not a copy or value of outer variable. It is, by magic of compiler, same variable in outer and inner function:

static void CapturingChangeTest()
{
int outer = 1;
Action x = () =>
{
Console.WriteLine(outer);
outer = 2;
};
outer = 3;
x();
Console.WriteLine(outer);
}

Code above is printing first 3, than 2. Creating x delegate is not same as executing it; therefore first dumped value will be 3. Next console dump is proof that outer is changed inside delegate.

LINQ

If you claim that you never used closure in C#, you are probably wrong, unless you skipped the best C# feature introduced in .NET 3.5 => LINQ.

You have hidden usage of closure in example below:

static List<int> CapturingUsage(List<int> list, int max)
{
return list.Where(x => x < max).ToList();
}

LINQ Where is just extension method and x => x < max is predicate made from lambda expression. If we dial down .NET version to 2.0 and replace Where extension method with FindAll List method, closure becomes more obvious:

static List<int> CapturingUsageFindAll(List<int> list, int max)
{
int outerCapturedVariable = max;
Predicate<int> isLessThanMax = delegate(int numberToTest)
{
return numberToTest < outerCapturedVariable;
};
return list.FindAll(isLessThanMax);
}

Without closure, we would need all kind of predicates in order to pass different parameters! Just because of closure we are not passing max to predicate and we can have one and only one type of predicate: Predicate<int> regardless of the query. And this is a big deal!

Problems

Now, let’s talk about problems related to captured variables. Captured variable will live as long as any delegate instance referring to it. If you are not careful, this could lead to unpredicted results and memory leaks.

static Action DemonstrateLifeOfCapturedVariable()
{
int capturedVariable = 0;
return () =>
{
Console.WriteLine(++capturedVariable);
};
}
var action = DemonstrateLifeOfCapturedVariable();
action();
action();
action();

Resulting output will be:

1
2
3

If capturedVariable would be on the stack of DemonstrateLifeOfCapturedVariable() it would be destroyed once method is finished. This is not what we see in the result above. What actually happened here is that compiler created one extra object to hold capturedVariable on the heap. Delegate has reference to this object and prevent it from garbage collection as long as delegate is alive.

Foreach Surprise

There is one more surprise related to captured variables. When switching from C#4 to C#5 your code could start producing different results! Note that running example below in VS2013 using C#5 will still work correctly regardless of .NET version targeted. It is a feature of language, not .NET framework!

int[] numbers = { 1, 2, 3 };
var actions = new List<Action>();
foreach(var number in numbers)
{
actions.Add(() => Console.WriteLine(number));
}
foreach(var action in actions)
{
action();
}

Result C#4:
Running code above in C#3 or C#4 will output 3 three times. All three actions are pointing to the same captured variable. This is hardly expected by someone not familiar with closures and variable capturing.

Result C#5:
C#5 compiler will create separate variable for each iteration loop so output will be 1 2 3 as expected. Hopefully, you have unit tests that will capture this or similar change as soon as you switch to new C# version.

To Remember

C# closures are one of the things people use without deeper thinking. If you need to remember one thing from the text above, remember life cycle of captured variable. Memory leak is difficult to catch, the other bugs you’ll find easy.