12

The following code outputs 33 instead of 012. I don't understand why a new variable loopScopedi isn't captured in each iteration rather than capturing the same variable.

Action[] actions = new Action[3];

for (int i = 0; i < 3; i++)
{

   actions [i] = () => {int loopScopedi = i; Console.Write (loopScopedi);};
}

foreach (Action a in actions) a();     // 333

Hopwever, this code produces 012. What's the difference between the two?

Action[] actions = new Action[3];

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

foreach (Action a in actions) a();     // 012
Mouhyi
  • 277
  • 2
  • 11
  • 1
    Ah this is a modified closure issue - see http://stackoverflow.com/questions/235455/access-to-modified-closure – Matt Roberts May 28 '13 at 21:40
  • 1
    Closure problem : http://www.codethinked.com/c-closures-explained for example. Behaviour is different in .net < 4.5 and >= 4.5 – Raphaël Althaus May 28 '13 at 21:41
  • 2
    Read these 2 entries from Eric Lippert http://blogs.msdn.com/b/ericlippert/archive/2009/11/16/closing-over-the-loop-variable-part-two.aspx and http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx – Icarus May 28 '13 at 21:54

4 Answers4

8

This is called "access to a modified closure". Basically, there is only one i variable, and all three lambdas are referring to it. At the end, the one i variable has been incremented to 3, so all three actions print 3. (Note that int loopScopedi = i in the lambda only runs once you call the lambda later.)

In the second version, you are creating a new int loopScopedi for every iteration, and setting it to the current value of i, which is 0 and 1 and 2, for each iteration.

You can try imagining inlining the lambdas to see more clearly what is happening:

foreach (Action a in actions)
{
   int loopScopedi = i; // i == 3, since this is after the for loop
   Console.Write(loopScopedi); // always outputs 3
}

Versus:

foreach (Action a in actions)
{
   // normally you could not refer to loopScopedi here, but the lambda lets you
   // you have "captured" a reference to the loopScopedi variables in the lambda
   // there are three loopScopedis that each saved a different value of i
   // at the time that it was allocated
   Console.Write(loopScopedi); // outputs 0, 1, 2
}
Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80
2

What's the difference between the two?

Different scope.

In your first loop you are referring to the i variable which is defined in the for loop statement scope while in the second loop you are using a local variable. The 333 output is as a result of the fact that your first loop iterates 3 times and accordingly the i variable is incremented to 3 eventually, then when you call the actions, they all refer to the same variable (i).

In the second loop you are using a new variable for each Action so you get 012.

Yair Nevet
  • 12,725
  • 14
  • 66
  • 108
2

Variables captured in a lambda are hoisted into a class shared between the lambda and the outside code.

In your first example, i is being hoisted once and used with both the for() and all the passed lambdas. By the time you reach Console.WriteLine, the i has reached 3 from the for() loop.

In your second example, A new loopScopedi is being hoisted for each run of the loop, so it is left unaffected by the subsequent loops.

Cory Nelson
  • 29,236
  • 5
  • 72
  • 110
2

This is about how C# handles closures. In first example closure will not be captured correctly and you will end up using the last value always; but in second example you are capturing the current value of the loop variable in a placeholder and then you use that placeholder; which provides the right solution.

And there is differences between how C# captures the loop variable in foreach loops and for loops in C# 5.0 and previous versions - a breaking change.

I had (almost) the same question and I learnt about it here.

Community
  • 1
  • 1
Kaveh Shahbazian
  • 13,088
  • 13
  • 80
  • 139