Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you capture iteration variables?

When you capture the iteration variable of a for loop, C# treats that variable as though it was declared outside the loop. This means that the same variable is captured in each iteration. The following program writes 333 instead of writing 012:

Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
    actions [i] = () => Console.Write (i);

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

I'm reading C# in a Nutshell (5th Edition) and today i came across this but i can't get my head over it, i don't get why the output is 333 and not 012. Is it because the value of i that's getting printed is the value after the loop? How is that possible? i is supposed to be disposed after the loop, isn't it?

like image 916
Asad Ali Avatar asked May 25 '14 16:05

Asad Ali


People also ask

What is a iteration variable?

The iterator (loop) variable is the variable which stores a portion of the iterable when the for loop is being executed. Each time the loop iterates, the value of the iterator variable will change to a different portion of the iterable.

What is meant by iteration variable in Python?

We call the variable that changes each time the loop executes and controls when the loop finishes the iteration variable. If there is no iteration variable, the loop will repeat forever, resulting in an infinite loop.


3 Answers

The variable i is captured inside the for loop but your are kind of extending the scope of it by doing so. So the variable is left at it's last state which was 3, hence the code outputting 333.

Another way to write the code is this:

Action[] actions = new Action[3];
int i; //declare i here instead of in the for loop

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

//Now i=3
foreach (Action a in actions) a(); // 333

The output is the same as writing:

Console.Write(i);
Console.Write(i);
Console.Write(i);
like image 73
DavidG Avatar answered Oct 24 '22 07:10

DavidG


Because the lambda captures last value of i, and that is 3.Step of your loop is executed for last time,then i becomes 3 and your loop ends.

I think this would make it clear for you:

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

Console.WriteLine(i);  // writes 3

You could fix this using a temporary variable:

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

foreach (Action a in actions) a(); // now: 012

I would recommend you to read this article to understand closures better

like image 30
Selman Genç Avatar answered Oct 24 '22 07:10

Selman Genç


My approach for understanding the closure in this case is to unroll the for loop:

var actions = new List<Action>();
// this loop is "executed"
for (int i = 0; i < 3; i++)
{
    actions.Add(() => Console.Write (i));
}
// pseudo "unroll" the loop
// i = 0
// action(0) = Console.WriteLine(i);
// i = 1
// action(1) = Console.WriteLine(i);
// i = 2
// action(2) = Console.WriteLine(i);
// i = 3

foreach (Action a in actions)
{
    a();
}
// pseudo "unroll" the foreach loop
// a(0) = Console.WriteLine(3); <= last value of i
// a(1) = Console.WriteLine(3); <= last value of i
// a(2) = Console.WriteLine(3); <= last value of i
// thus output is 333

// fix
var actions = new List<Action>();
// this loop is "executed"
for (int i = 0; i < 3; i++)
{
    var temp = i;
    actions.Add(() => Console.Write (temp));
}
// pseudo "unroll"
// i = 0
// temp = 0
// actions(0) => Console.WriteLine(temp); <= temp = 0
// i = 1
// temp = 1
// actions(1) => Console.WriteLine(temp); <= temp = 1
// i = 2
// temp = 2
// actions(2) => Console.WriteLine(temp); <= temp = 2
foreach (Action a in actions)
{
    a();
}
// pseudo "unroll" the foreach loop
// a(0) = Console.WriteLine(0); <= last value of first temp
// a(1) = Console.WriteLine(1); <= last value of second temp
// a(2) = Console.WriteLine(2); <= last value of third temp
// thus 012
like image 23
keenthinker Avatar answered Oct 24 '22 07:10

keenthinker