While experimenting with closures in C# I found out that they work rather unexpectedly if they capture an iterator variable in a loop.
var actions = new List<Action>();
foreach (int i in new[] { 1, 2 })
actions.Add(() => Console.WriteLine(i));
for (int i = 3; i <= 4; i++)
actions.Add(() => Console.WriteLine(i));
foreach (var action in actions)
action();
The above code produces a strange result (I'm using .NET 4.5 compiler):
1
2
5
5
Why is the value of i
captured differently for 2 almost identical loops?
In C# 5 and beyond, the foreach
loop declares a separate i
variable for each iteration of the loop. So each closure captures a separate variable, and you see the expected results.
In the for
loop, you only have a single i
variable, which is captured by all the closures, and modified as the loop progresses - so by the time you call the delegates, you see the final value of that single variable.
In C# 2, 3 and 4, the foreach
loop behaved that way as well, which was basically never the desired behaviour, so it was fixed in C# 5.
You can achieve the same effect in the for
loop if you introduce a new variable within the scope of the loop body:
for (int i = 3; i <= 4; i++)
{
int copy = i;
actions.Add(() => Console.WriteLine(copy));
}
For more details, read Eric Lippert's blog posts, "Closing over the loop variable considered harmful" - part 1, part 2.
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