0

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.

Denis Sivtsov
  • 120
  • 1
  • 12
  • 1
    I don't understand what you are looking for in a satisfactory answer beyond Eric Lippert's linked answer and blog. – David L Feb 07 '23 at 01:52
  • He explained good the was existed problem and how they did, but didn't explain why they did it, for me it interesting for deep dive in c# details – Denis Sivtsov Feb 07 '23 at 11:04
  • After additional iteration of reading stackoverflow i found [question](https://stackoverflow.com/q/70238061/10951191) that can give answer on Q1 from here - the final decision was made not under technical reason, but based on psychological reason - the most beginner developers which began use the LINQ & foreach (very popular connection) will initially hope that the closure Lambda in foreach will be did "by value", like in java before 7 with `final` [variables](https://csharpindepth.com/Articles/Closures) – Denis Sivtsov Feb 07 '23 at 13:59
  • Does this answer your question? [Closure and Linq](https://stackoverflow.com/questions/70238061/closure-and-linq) – TylerH Feb 08 '23 at 14:42
  • @TylerH No, It's a useful information ("about how it works on simple example"), but doesn't explain why it was realized in that way ("for cycle" closure Lambda "by ref" and "foreach" closure "by "val"). Eric Lippert gave in his blog the good explanation the problematic of this question, but I didn't find why "foreach" was changed? Above Upper, I'm gave my explanation why it was may was did, but for me interested to receive explanation from more experience c# developers – Denis Sivtsov Feb 08 '23 at 16:44

1 Answers1

8

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 the for.

(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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067