Hierarchie list-like interfaců
interface IEnumerator<out T> {
    T Current { get; private set; }
    bool MoveNext();
    void Dispose();
    void Reset();   // nastavi Current na prvni prvek, realne to je k nicemu
    // pro nejake slozitejsi typy muze byt obtizne zjistit puvodni start
    // takze casto Reset() vyhodi NotImplementedException
}
interface IEnumerable<out T> {
    IEnumerator<T> GetEnumerator();
}
interface IReadOnlyCollection<out T> : IEnumerable<T> {...}
interface IReadOnlyList<out T> : IReadOnlyCollection<T> {...}
interface ICollection<T> : IEnumerable<T> { int Count; ...}
interface IList<T> : ICollection<T> {...}
class T[] : IReadOnlyList<T>, IList<T> {...}
class List[] : IReadOnlyList<T>, IList<T> {...}
// z nejakych dobrych duvodu jsou oba parametry invariant
interface IReadOnlyDictionary<TKey, TValue> {...}
interface IDictionary<TKey, TValue> {...}
Z historických důvodů existují negenerické IEnumerator a IEnumerable od kterých dědí ty generické, takže musím Current nebo GetEnumerator implementovat genericky i negenericky.
Closer look at IEnumerable
interface IEnumerable<T> : IEnumerable {
    IEnumerator<T> GetEnumerator();
    // explicitne implementuju interface IEnumerable
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
interface IEnumerator<T> : IEnumerator, IDisposable {
    T Current { get; }
    bool MoveNext();
    void Reset();  // casto k nicemu --> NotImplementedException
    void Dispose();
}
var a = new List<int> {1, 2, 3};
var e = a.GetEnumerator();
// (pred prvnim MoveNext) --> abychom mohli mit prazdne kolekce
e.Current;  --> InvalidOperationException
while (e.MoveNext()) {
    e.Current;  // 1, 2, 3
}
It is possible to make infinite collections
public class FactorialEnumerable : IEnumerable<int> {
    public IEnumerator<int> GetEnumerator() {
        return new Enumerator();
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    // tim ze je to private vnoreny typ, tak zvenci neni pristupny
    private class Enumerator : IEnumerator<int> {
        private int _counter = 0;
        public int Current { get; private set; }
        object IEnumerator.Current => Current;
        public bool MoveNext() {
            if (_counter == 0) {
                Current = 1;
            } else {
                Current *= _counter;
            }
            _counter++;
            return true;
        }
        public void Reset() {
            _counter = 0;
        }
        public void Dispose() {}
    }
}
Concurrent modification
Když enumeruju list a současně se snažím odstraňovat nějaké prvky. Povede to na InvalidOperationException. Řekněme že mám [1,2,3,4,5], enumerátor je na 2, já odstraním 2 a tím se posune 3 o jeden index zpět. Zavolám MoveNext(), pak Current a dostal bych 4. Takže jsem vyrobil race condition bez vláken. Podporovat concurrent modification by bylo drahé na paměť, proto to kolekce standardně nedělají, ale měly by to nějak detekovat a vyhodit InvalidOperationException.
var a = new List<int> {1, 2, 3, 4, 5};
var e = a.GetEnumerator();
int index = 0;
while (e.MoveNext()) {
    if (e.Current % 2 == 0) {
        a.RemoveAt(index);
    }
    index ++;
}
Jak funguje foreach
foreach (ElemT item in collection) statement;
// se prelozi zhruba na
IEnumerator enumerator = ((IEnumerable)collection).GetEnumerator();
try {
    ElemT element;
    while (enumerator.MoveNext()) {
        element = (ElemT)enumerator.Current;
        statement;
    }
} finally {
    IDisposable disposable = enumerator as IDisposable;
    // kdyby to nebylo IEnumerable<T>, tak tohle Dispose() nezavola
    // protože negenericky IEnumerator neimplementuje IDisposable
    if (disposable != null) disposable.Dispose();
}
Reálně nejprve zkusí zavolat prostě collection.GetEnumerator, až pak na IEnumerable<T>.GetEnumerator a nakonec IEnumerable.GetEnumerator. Takže collection nemusí implementovat IEnumerable. Ta metoda GetEnumerator může být klidně extension metoda.
Opravdu tam je ta explicitni konverze, takže lze dělat třeba
foreach (byte b in new List<int> {1, 2, 3})
Nebo pokud neexistuje implicitni ani explicitni C# konverze, tak to hleda user defined konverzi.
Iterátorové metody
Nejprve motivacni priklad, ze psat enumeratory rucne je dost otravne. Mame tridu A, ktera ma dve pole a chceme je enumerovat za sebou. Misto
A a = new A();
foreach (int x in a.x1) Console.WriteLine(x);
foreach (int x in a.x2) Console.WriteLine(x);
// cheme
foreach (int x in a) Console.WriteLine(x);
class A : IEnumerable<int> {
    private int[] x1 = { 1, 2 };
    private int[] x2 = { 3, 4, 5 };
    public IEnumerator<int> GetEnumerator() => new Enumerator(this);
    private class Enumerator : IEnumerator<int> {
        private A a;
        private int index = -1;
        private bool firstArray = true;
        public Enumerator(A a) => this.a = a;
        public int Current {
            get {
                if (firstArray) {
                    if (index < 0) {
                        throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
                    } else {
                        return a.x1[index];
                    }
                } else {
                    if (index >= a.x2.Length) {
                        throw new InvalidOperationException("Enumeration already finished.");
                    } else {
                        return a.x2[index];
                    }
                }
            }
        }
        public bool MoveNext() {
            if (firstArray) {
                if (index < a.x1.Length - 1) {
                    index++;
                    return true;
                } else {
                    index = -1;
                    firstArray = false;
                }
            }
            if (index < a.x2.Length - 1) {
                index++;
                return true;
            } else {
                if (index < a.x2.Length) index++;
                return false;
            }
        }
        public void Reset() { }
        public void Dispose() { }
    }
}
V podstate musim udelat nejaky jednoduchy stavovy automat. Proto ma C# iteratorove metody, ktere tohle delaji za me. Ale porad se to preklada do nejakeho konecneho automatu co uvnitr pouziva switch a goto. Tu stejnou vec bych napsal jako
public IEnumerator<int> GetEnumerator() {
    for (int i = 0; i < x1.Length; i++) {
        yield return x1[i];
    }
    for (int i = 0; i < x2.Length; i++) {
        yield return x2[i];
    }
}
Musí vracet nějaký IEnumerator a obsahovat klíčové slovo yield return.
IEnumerator<int> GetData(...) {
    // kod alpha
    yield return 1 + f(1);
    // kod beta
    yield return 2 + f(2);
    // kod gamma
}
Když poprvé volám MoveNext(), tak se provede alpha a spočítá se 1+f(1). Podruhé se provede beta a spočítá se 2+f(2) a napotřetí se provede gamma a vrátím false, protože enumerátor už nemá co vracet.
To co vrací yield return se zapamatuje do nějakého vnitřního stavu, takže GetCurrent() vrátí ten uložený stav. GetCurrent() před alpha a po gamma by měl vyhazovat InvalidOperationException, ale kvůli historickým důvodům to tak není. Před alpha to vrátí nějakou defaultní hodnotu toho T a po gamma to vrátí poslední uloženou hodnotu.
C# překladač z toho vyrobí enumerátor, který má ten náš kód alpha, beta a gamma uvnitř MoveNext() (implementace pomocí state machine) a z té metody GetData() vygeneruje metodu, která jen vyrobí a vrátí ten enumerátor.
- Lokální proměnné z GetData() se převedou na fieldy toho enumerátoru.
- V Release režimu si překladač všimne, že některé lokální proměnné není třeba držet globálně a přeloží je jako lokální (ne jako fieldy).
- Parametry iter. metody se taky ulozi do enumeratoru jako private fieldy - capture by value.
- stavovy automat pres switch a goto, ma stav _state- na zacatku je _state = 0
- koncovy stav je _state = -1
- jinak podle toho co se v te metode deje jsou tam dalsi stavy
- kdyz zavolam MoveNext(), tak se_statenejprve nastavi na -1 a az kdyz to dobehne, tak zase na neco korektniho
- duvod: kdyby se nejak pokazil vnitrni stav toho enumeratoru, tak budu v koncovem stavu, coz ma deterministicke chovani
 
- na zacatku je 
POZOR: veskery kod te metody skonci v MoveNext(), takze pokud tam delam treba nejakou kontrolu parametru a vyhazuju vyjimku, tak se to provede az pri prvnim volani MoveNext(), coz bude typicky v nejakem foreach cyklu. NE PRI VYTVARENI ITERATORU.
POZOR: foreach cyklus je pro IEnumerable, ne pro IEnumerator, takže reálně musím používat MoveNext(), pokud s ním chci pracovat
Klíčové slovo yield break enumerátor rovnou přenese do koncového stavu, takže enumerování skončí a MoveNext() bude vracet false.
Prevod lazy evaluation na eager evaluation
System.LINQ ma metody IEnumerable.ToList a .ToArray. Muze se hodit. POZOR: ToArray je trochu pomalejsi, protoze nevime pocet prvku v kolekci. Takze se to stejne musi kopirovat do nejakeho nafukovaciho pole a navic nakonec prekopirovat do toho finalniho pole.
- pri lazy evaluaci je potreba dat si pozor na concurrent modification
Iteratorove metody II
Standardne iteratorove metody vraci IEnumerator<T>, ale mohou vracet i IEnumerable<T>.
class A {
    public int x = 0;
    IEnumerable<int> Range(int from, int to) {
        for (int i = from; i < to; i++) {
            yield return x + i;
        }
        x += 100;
    }
}
Tohle se pri prekladu rozpadne na dve tridy.
class A {
    public int x = 0;
    IEnumerable<int> Range(int from, int to) {
        var alpha = new Alpha();
        alpha._from = from;
        alpha._to = to;
        alpha._this = this; // protoze dochazi ke zmene this.x
        return alpha;
    }
    private class Alpha : IEnumerable<int> {
        // capture params by value
        public int _from;
        public int _to;
        public A _this;
        public IEnumerator<int> GetEnumerator() {
            var beta = new Beta();
            beta._from = _from;
            beta._to = _to;
            beta._this = _this;
            return beta;
        }
    }
    private class Beta : IEnumerator<int> {
        // captured local vars by move -- "presunou" se sem
        // recapture params by value
        public int _from;
        public int _to;
        public A _this;
        int _state = 0;
        public bool MoveNext() { state machine }
    }
}
Proc se te Bete nepreda odkaz na Alpha, ale deje se znovu capture params? Protoze v te Alpha to je nejaky sdileny stav pro vsechny enumeratory a nechci ho menit z nejakeho konretniho enumeratoru… V te iter. metode jsou from a to jsou nejake lokalni promenne, ktere teoreticky muzu menit.
Ve skutecnosti to vsechno dela nejaka jedna trida ktera implementuje IEnumerable i IEnumerator. Ale semanticky to je takhle.