What will be the output of the following code?
using System;
using System.Collections.Generic;
class Test
{
delegate void Printer();
static void Main()
{
List<Printer> printers = new List<Printer>();
for (int i = 0; i < 10; i++)
{
printers.Add(delegate { Console.WriteLine(i); });
}
foreach (Printer printer in printers)
{
printer();
}
}
}
Most of us will expect to see the numbers 0 to 9 but in reality this code prints out ten times ten!
Why is that happening?
In the for loop there is only one variable i used and that’s incremented in each run. The delegate constructed in the loop points to the instance of variable i, not to the value. So at the end of the loop, the variable i has the value 10 and all delegates point to that.
This has to do with Captured Variables.The function Console.WriteLine(i); has to know about which i we are talking. The compiler generates a class in the background that captures the necessary variables and rewrites your code for you to make all the magic happen.
Here is an example that shows what the compiler is doing for us:
static void Main(string[] args)
{
Console.WriteLine("Variable Capturing");
string name = "Matthew";
Func<String> capture = () => name;
name = "Mallory";
Print(capture);
}
This is translated into something like this (there are differences in naming conventions and stuff):
public class Capture
{
public string name;
public string Lambda()
{
return this.name;
}
}
static void Main(string\[\] args)
{
Capture capture = new Capture();
Console.WriteLine("Variable Capturing");
capture.name = "Matthew";
capture.name = "Mallory";
Print(capture.Lambda);
}
static void Print(Func<string\> capture)
{
Console.WriteLine(capture());
}
Here you can clearly see why the value of name has changed when calling Print();
The thing to remember is that ‘Closures close over variables, not over values’.
Currently, in C# 4 this behavior is also happening with the foreach statement. However, C# 5 will make the breaking change of making sure that the loop variable will be inside the loop!
References: