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
3

4 Answers 4

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
}
Sign up to request clarification or add additional context in comments.

Comments

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.

Comments

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.

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.