Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# declaring variables inside Lambda expressions

Tags:

c#

lambda

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
like image 547
Mouhyi Avatar asked May 28 '13 21:05

Mouhyi


2 Answers

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
}
like image 148
Dave Cousineau Avatar answered Oct 18 '22 22:10

Dave Cousineau


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.

like image 29
Cory Nelson Avatar answered Oct 19 '22 00:10

Cory Nelson