Overhead/Performance of C# IEnumerables implemented with yield return
12:10 30 Jun 2026

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?

c# .net performance mono implementation