This is part 10/17 of my Exploring the .NET CoreFX series.
The .NET Core’s System.Collections.Immutable.ImmutableArray provides two enumerators. The first has been highly tuned for speed, and the second is a fallback for compatibility when it is required.
The high-performance enumerator uses the following performance optimizations:
- The enumerator is a
struct, rather than aclass, so that it is stack-allocated rather than heap-allocated. - The enumerator does not implement
IEnumeratororIEnumerator, as this would require it to implementIDisposable. By not implementingIDisposablethe iterator will inline duringforeachloops.. - The enumerator does not use range checks in
Enumerator.Current; it requires on .NET’s array range checks to throw an exception instead.
The high-performance enumerator is called ImmutableArray.Enumerator, which is returned by ImmutableArray.GetEnumerator():
[Pure]
public Enumerator GetEnumerator()
{
this.ThrowNullRefIfNotInitialized();
return new Enumerator(this.array);
}
Note that this method is not defined by any interface ImmutableArray implements, but it will be used by foreach. This is because foreach is pattern-based rather than interface-based. What this means is that foreach does not require an object to implement IEnumerable as long as it implements a method named GetEnumerator(). Similarly, the returned object is not required to implement IEnumerator as long as it implements a Current property and a MoveNext() method.
The result of all this work is that foreach over an ImmutableArray is just as efficient as a hand-written for loop.
Recommendations
- To improve the performance of foreach, consider writing a specialized, struct-based enumerator in addition to the traditional one.