Monday 21 July 2014

C# 5.0 Breaking Change to Closures

In C# a lambda can capture local variables from the scope in which it's defined, and lambdas that do so are called closures. Captured variables are evaluated when the lambda is invoked, not when the variables were captured. For example, the following code:

int age = 30;
Action printAge = () => Console.WriteLine(age);
age++;
printAge();

prints 31, not 30.

Up until C# 4.0 there was one context in which closures didn't act as most people would expect. Consider the following:

var actions = new Action[3];
int i = 0;

foreach (var c in "dog") {
  actions[i++] = () => Console.Write(c);
}

foreach (var action in actions) {
  action();
}

Prior to C# 5.0 this printed "ggg". In effect the variable c persisted over each iteration of the first foreach, making it equivalent to:

char c;
for (int i = 0; i < 3; i++) {
  c = "dog"[i];
  actions[i] = () => Console.Write(c);
}

On the third time through the loop c is set to "g". Therefore, when the closures are executed c is evaluated to be "g", and so that's what's printed out three times.

If you wrote the loop using for as shown above then this is what you should expect. However, if you used the original foreach you would probably expect the output to be "dog", and starting with C# 5.0 that's what it is. In effect, from C# 5.0 on, the foreach loop is equivalent to:

for (int i = 0; i < 3; i++) {
  char c = "dog"[i];
  actions[i] = () => Console.Write(c);
}          

The subtle difference here is that the variable c is declared locally within each iteration of the foreach loop. Therefore, each closure captures a different variable, and it's value isn't changed from the time it's captured until the time it's evaluated.

Note this is a breaking change as code compiled prior to and since C# 5.0 will behave differently. However, it was felt that the change was worth it as it cleared up confusing behaviour.