Допустим есть метод-итератор:

static IEnumerable Bar() { yield break; }

Попробуем вызвать его и поковырять возвращённый IEnumerable-объект:

var x = Bar();
Console.WriteLine(x == x.GetEnumerator()); // true
Console.WriteLine(x == x.GetEnumerator()); // false

Интересный эффект (оператор == тут действует как проверка ссылочной эквивалентности)… Дело тут в том, что в C# генерирует для итератора всего один класс, который реализует и интерфейс IEnumerable, и IEnumerator. При этом по вызову GetEnumerator() он должен фактически вернуть себя самого, что и происходит в первом вызове. Однако на следующие вызовы переиспользовать самого себя как IEnumerator класс уже не может, поэтому создаёт и возвращает свою копию. Однако это не все эффекты:

var y = Bar();
var t = new Thread(() => {
  Console.WriteLine(y == y.GetEnumerator()); // false
  Console.WriteLine(y == y.GetEnumerator()); // false
});

t.Start();
t.Join();

То есть при вызове из потока, отличного от того, в котором экземпляр IEnumerable был получен вызовом метода-итератора, создаётся новый экземпляр. Это сделано во избежании ситуации, когда несколько потоков могут обратиться к “свежему” IEnumerable одновременно и разделить между собой один и тот же IEnumerator.

Кстати, если бы метод-итератор возвращал IEnumerator, то никаких подобных проверок и переиспользований компилятор C# не генерировал бы.

Подробное описание всех implementation details итераторов можно посмотреть в reflector’е или почитать здесь.