6

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?

holdenmcgrohen
  • 1,031
  • 2
  • 9
  • 30
  • It is clever of you to notice this subtle distinction. And yes, there is a danger here that someone could refactor a foreach into a for and not realize they were introducing a semantic change! I am interested to know if you have a "real world" usage case for the "for" case, particularly one where the odd behaviour of the "for" is the desired behaviour. – Eric Lippert Feb 12 '16 at 15:01

2 Answers2

11

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
0

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.

Ehsan Sajjad
  • 61,834
  • 16
  • 105
  • 160