Closures behaving differently in for and foreach l

2019-02-22 10:38发布

问题:

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?

回答1:

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.



回答2:

In foreach case it holds the value in a local variable so it has its own value for every delegate while in for loop case it is not like that, in for loop case all the the delegates are referring to same i so the last value updated in the i is used by all delegates.

That was a breaking change in case of foreach loop, in old versions they both used to be work same way.