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?
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.
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.
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);
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With