This is a detailed answer about how C# compiler optimizes foreach
in the cases when IEnumerator<T>
is a mutable struct.
Does F# compiler perform the same optimization?
This is a detailed answer about how C# compiler optimizes foreach
in the cases when IEnumerator<T>
is a mutable struct.
Does F# compiler perform the same optimization?
AFAICT It seems F# treats valuetype enumerators in a similar manner as C#:
Here is a disassembled snippet of IL code from a simple C# program that uses foreach over an IEnumerable<T>
.locals init (
[0] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>,
[1] int32 v
)
IL_0025: ldloca.s 0
IL_0027: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
Note that local 0
is a valuetype and that it uses ldloca.s
to load the address of the struct.
Compare it with F#
.locals init (
[0] class [mscorlib]System.Collections.Generic.List`1<int32> ra,
[1] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
)
IL_000e: ldloca.s 1
IL_0010: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
This code also uses valuetype to declare local 1
and ldloca.s
to load the address of the struct.
As a side note: Later F# version does actually do an optimization that C# don't do. Because it's a common pattern in F# to iterate over F# lists that are immutable data structures it's ineffective to iterate using enumerators. So F# has a special case for lists and applies a more efficient algorithm in that case. In C# iterating over F# lists would fallback to enumerators.
It would be possible to implement a special handling for IList
types as well but since it's possible someone has implement IList
in a "funny" way it's a potentially breaking change to implement such an optimization.