Closures behaving differently in for and foreach l

2019-02-22 10:23发布

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?

2条回答
Root(大扎)
2楼-- · 2019-02-22 10:59

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.

查看更多
Bombasti
3楼-- · 2019-02-22 11:01

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.

查看更多
登录 后发表回答