Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Closures behaving differently in for and foreach loops

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?

like image 766
holdenmcgrohen Avatar asked Feb 12 '16 14:02

holdenmcgrohen


1 Answers

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.

like image 129
Jon Skeet Avatar answered Oct 24 '22 01:10

Jon Skeet