Iterators
Muito antigamente, quando se programava em C# 1.0, caso você precisasse implementar as interfaces IEnumerable ou IEnumerator era necessário fazer na mão, implementando os métodos Current, MoveNext e Dispose para o IEnumerator e GetEnumerator para o IEnumerable.
O mais complicado era o MoveNext(), pois é necessário criar uma máquina de estado para saber qual resultado deve ser atribuido ao Current.
Estas interfaces são conceitos importantes no C# e na plataforma .NET em geral, o foreach funciona baseado na classe IEnumerable, chamando seu único método GetEnumerator que por sua vez retorna um object que implementa a interface IEnumerator.
Com o C# 2.0, uma nova keyword e muito trabalho do compilador, é possível simplificar tudo isso. Usando o yield return no seu método ou no get da propriedade o compilador se encarrega de criar uma classe aninhada que implementa essas interfaces pra você, controlando todos os fluxos respeitando if´s, for´s blocos finally, etc. É um trabalho enorme por parte do compilador e claro da equipe do compilador do C# que trabalhou duro no algoritmo. Hoje essa "feature" é uma das maiores no compilador do C#.
Nada melhor do que vermos o código para entender melhor como tudo isto funciona. Vou mostrar um método que retorna IEnumerator<int> e o resultado gerado pelo compilador, usando o Reflector pra isso.