Page 1 of 3
Closure sounds like something a therapist might recommend but we explain how it applies to anonymous methods and lambda expressions and, more importantly, why it is useful.
Both anonymous methods and lambda expressions bring with them an interesting additional facility.
As both are declared within the context and scope of an enclosing method there is the possibility of defining how the enclosing and enclosed method can interact - and this brings us to the fascinating and useful topic of closure.
Consider the following code:
int i = 0;
MyHelloDelegateType1 Hello2 =
You might find it surprising that this code is legal, let alone that it reveals that the value of i is incremented each time the delegate is called and the final statement shows that it is indeed the local variable that is changed.
This behaviour is described by saying that the anonymous method “captures” the variables in the scope of its containing, or outer, function.
Many programmers refer to this as “closure” or “lexical closure”, although there is much debate about what exactly constitutes closure and you will find some saying that C# doesn’t support it and others that it does.
The issue comes down to whether the value or the variable is captured at the time of creation.
C# captures the variable and, for me at least, this is good enough to be called closure.
Notice that closure operates because one method is defined nested within the scope of another method. This happens when you declare anonymous methods and lambda expressions. In the rest of the article the examples are all based on anonymous methods but all of the principles apply just as much to lambda expressions.
To demonstrate how subtle the effects of closure can be consider the following example:
Notice that we create an array of 10 delegates and each one is the same anonymous method that simply displays the current value of i. What do you think is going to be the result of calling one of the delegates, Count say?
The first thing to notice is that i isn’t even in scope at the end of the for loop so if you use:
after the for loop then you will get runtime error:
“The name 'i' does not exist
in the current context”
With this in mind you might expect that calling one of the delegates would produce the same error, but no:
for (int j = 0; j < 10; j++)
works perfectly and displays the value 10 for each delegate.
What happens is that the variable i is captured when each of the delegates is created but all of the delegates share the same variable with the local environment.
When the outer function changes the variable then all the delegates see the change and, in this case, the delegates’ captured copy of i slowly counts up to 10.
When the loop ends the local version of the variable goes out of scope but the captured copy of i lives on in the delegates and it has the value 10.
It doesn’t make any difference if you change the final loop variable from j to i – this is a different i in a different lexical context and nothing to do with the captured variable i that the delegates are using.
Examples of closure can become more complicated than this simple for loop and if you find yourself using such constructions you probably should reconsider and find a more clear expression of what you are trying to do.
However the principle is simple enough; the compiler creates a hidden class wrapper for all of the variables in scope when the delegates are created. If a variable is recreated each time the delegate is created then each delegate will capture a new copy.
int j = i;
In this case the variable j is recreated each time through the loop and each delegate captures its own copy.
If you now try calling each delegate in turn you will find that it now displays 0,1,2, and so on, reflecting the value of i at the time the delegate was created.
Notice that j is out of scope when the loop ends so you can’t discover what its current value is – only the captured copies survive the loop.