No such guarantee exists.
The only guarantee there is is that enumeration will throw on next iteration after a modification.
The compiler can assume nothing.
That's an overstatement. That's currently impossible in C#, but Microsoft is working on it in F*: https://www.fstar-lang.org I hope some of that will trickle down to F# and C# some day.
The compiler can explore the call graph of the loop body and find out which methods might be called on the list, and then check that none of those methods write to the list's internal length variable.
How deeply and precisely the compiler (or JIT) analyzes the code in an attept to prove that the length won't change involves a tradeoff in compilation speed, but I'd expect .NET to have found a pretty good sweet spot by now.
For a single thread that should be doable. But that’s not mathematically covering all bases is it?
And how can the compiler know if the relevant list is not accessed from multiple threads? I’m pretty sure that’s next to impossible and definitely not worth the complexity for such a small optimisation.
Doing some quick googling, this article seems to explain things well from the perspective of a C# developer: https://afana.me/archive/2015/07/10/memory-barriers-in-dot-n...
For example, see https://shipilev.net/jvm/anatomy-quarks/19-lock-elision/.
This also can help when using java’s StringBuffer, which is thread-safe, but can often be ‘converted’ into a non-thread-safe variant by the compiler (the existence of StringBuilder shows that ‘often’ at least in some version of Java wasn’t often enough, though)
This is not true. Here's three examples:
A positive result from escape analysis and then scalar replacement of aggregates.
A positive result from partial escape analysis until after this point in the program.
Speculation that list is not modified, and deoptimisation if it is.