0

The code snippet below will output the number '10' ten times:

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 (var printer in printers)
      {
           printer();
      }
}

This is because (taken from https://www.toptal.com/c-sharp/interview-questions#iquestion-90455):

"the delegate is added in the for loop and “reference” to i is stored, rather than the value itself. Therefore, after we exit the loop, the variable i has been set to 10, so by the time each delegate is invoked, the value passed to all of them is 10."

My question is: Why is "reference" to i is stored?

Not So Sharp
  • 1,149
  • 2
  • 10
  • 20

2 Answers2

6

This is because "the delegate is added in the for loop and “reference” to i is stored

No, that is not the issue. The issue is the way the delegate and the referenced values are extracted. That is called closure. The delegate is extracted from the loop and only the last value of i is kept since the closure is ran after the loop. (If you would call it halfway, it would return the value from that time).

See this blog post how the delegate ends up getting compiled at the seemingly wrong place.

This is the code it uses to demonstrate the issue:

Func func1 = null;

Program.<>c__DisplayClass2 class1 = new Program.<>c__DisplayClass2(); // <-- problem

class1.i = 0;

while (class1.i < count)
{
   if (func1 == null) // <-- more problems to follow
   {
      func1 = new Func(class1.<fillFunc>b__0); // <-- yikes: just one func!
   }

   Program.funcArr[class1.i] = func1;
   class1.i++; // <-- and just one i
}
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • According to https://blogs.msdn.microsoft.com/ericlippert/2009/11/12/closing-over-the-loop-variable-considered-harmful/ with the newer versions of C#, this issue no longer exists? – Not So Sharp Apr 21 '17 at 05:52
1

This is not an effect of by-value vs. by-reference.

The output is 10 because the value of variable i is evaluated when the delegate code runs, which is in the second loop, and by that time will have assumed the value of 10 because that's what it ends up with when the first loop exits.

Using the debugger to step through the calls to printer() in the second loop, you will find that the delegate closure takes the value of i in its original scope current at the time of the call.

Cee McSharpface
  • 8,493
  • 3
  • 36
  • 77
  • Shouldn't be as `i` isn't available outside the loop – EpicKip Apr 19 '17 at 08:41
  • @EpicKip It is. See the blog post I linked in my answer. – Patrick Hofman Apr 19 '17 at 08:44
  • See : https://blogs.msdn.microsoft.com/ericlippert/2009/11/12/closing-over-the-loop-variable-considered-harmful/ & https://blogs.msdn.microsoft.com/ericlippert/2009/11/16/closing-over-the-loop-variable-part-two/ – PaulF Apr 19 '17 at 08:47