TechQuiz Question 6: Getting some closure…

2012-05-12

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:

  1. Eric Lippert on closures
  2. Jon Skeet on closures
  3. Understanding Variable Capturing in C#