What is the overhead generated by functions like the in the example below?
public IEnumerable Range(int end) {
for (int i = 0; i < end; i++)
yield return i;
}
What does it do compared to a custom implementation of the state machine like either of these:
public readonly struct RangeImpl : IEnumerable {
public readonly int end;
public RangeImpl GetEnumerator() => this;
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(end);
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(end);
public RangeImpl(int end) { this.end = end; }
private struct Enumerator : IEnumerator {
private int _i, _end;
public int Current => _i;
object IEnumerator.Current => _i;
public Enumerator(int end) {
_i = 0;
_end = end;
}
public bool MoveNext() => ++_i < _end;
...
}
}
public struct RangeImplSimple {
private int _i;
public readonly int _end;
public readonly int Current => _i;
public RangeImplSimple(int end) {
_i = 0;
_end = end;
}
public bool MoveNext() => ++_i < _end;
public readonly RangeImplSimple GetEnumerator() => this;
}
A test of simply evaluating each range over 100000000 items resulted in the following average times:
simple for loop: 241 ms
yield enumerable: 931 ms
custom enumerable: 838 ms
custom enumerator without interface boxing: 645 ms
I could only find information about how yield behaves, but not what the compiler will generate and what exactly happens under the hood. Can someone explain the times I measure?