I read different articles regarding this theme Eric Lippert's blog and other here and know why this two code will work differently
int[] values = { 7, 9, 13 };
List<Action> f = new List<Action>();
foreach (var value in values)
f.Add( () => Console.WriteLine("foreach value: " + value));
foreach (var item in f)
item();
f = new List<Action>();
for (int i = 0; i < values.Length; i++)
f.Add(() => Console.WriteLine("for value: " + ((i < values.Length) ? values[i] : i)));
foreach (var item in f)
item();
But I didn't find clean explanation why eventually (begin from compiler c# version 5) was decided to made " the foreach loop variable will be logically inside the body of the loop, and therefore closures will get a fresh copy every time." Because the old realization give a more freedom to use the closures Lambda ("by value or by ref") but demanded from programmer to use it carefully and if you needed the current implementation from foreach you should was used the following code:
foreach(var v in values)
{
var v2 = v;
funcs.Add( ()=>v2 );
}
QUESTIONS:
Q1. I wonder why in the end it was decided to change the implementation of foreach (pros and cons I read, it was 50/50 Eric Lippert's blog)?
Q2. In the case of a "for" loop, it's a value that falls outside the loop's operating range (this creates a very specific situation where the lambda gets a value that you'll never get in the loop), why is it "out of control" because it's a very error prone situation ? (question number 2 is more rhetorical therefore can be skipped)
Additional explanation (for Q1) It would be interesting to know the reasons why this implementation was chosen - why the developers of C#, starting with version 5, changed the principles of closure for the foreach loop. Or why they did it for the foreach loop but didn't do it for the for.
I wonder why in the end it was decided to change the implementation of
foreach
Because (1) users strongly believed that the compiler's behaviour was at best unexpected, and at worst, simply wrong, and (2) there was no compelling reason to keep the strange behaviour. The biggest factor causing the design team to NOT take a breaking change is "because real code depends on the current behavior", but real code that depends on that behaviour is probably wrong!
This was a fairly easy call to make. Lots of people complained about the behaviour; no one at all complained about the fix. It was a good call.
why they did it for the
foreach
loop but didn't do it for thefor
.
(1) people didn't complain about the for
loop, and (2) the change would be much more difficult, and (3) the change would be much more likely to produce a real-world break.
One reasonably expects that the "loop variable" of a foreach
is not a "real" variable. You don't ever change it; the runtime changes it for you:
foreach(char c in "ABCDEFG")
{
c = 'X'; // This is illegal! You cannot treat c as a variable.
}
But that's not true of the loop variable(s) of a for
loop; they really are variables.
for(int i = 0; i < 10; i += 1)
i = 11; // weird but legal!
for
loops are much more complicated. You can have multiple variables, they can change value arbitrarily, they can be declared outside the loop, and so on. Better to not risk breaking someone by changing how those variables are treated inside the loop.