El ejemplo de código en particular que usted está buscando en implica una serie de transformaciones. Tenga en cuenta que esta es una descripción aproximada del algoritmo. Los nombres reales utilizados por el compilador y el código exacto que genera pueden ser diferentes. Entonces la idea es la misma, sin embargo.
La primera transformación es la transformación "foreach", que transforma este código:
foreach (var x in y)
{
//body
}
en este código:
var enumerator = y.GetEnumerator();
while (enumerator.MoveNext())
{
var x = enumerator.Current;
//body
}
if (y != null)
{
enumerator.Dispose();
}
La segunda transformación encuentra todos los estados de retorno rendimiento en el cuerpo de la función , asigna un número a cada uno (un valor de estado) y crea una "etiqueta de ir" justo después del rendimiento.
La tercera transformación levanta todas las variables locales y los argumentos de funciones en el cuerpo del método en un objeto llamado cierre.
Dado el código en tu ejemplo, que se vería similar a esto:
class ClosureEnumerable : IEnumerable<string>
{
private IEnumerable<string> args;
private ClassType originalThis;
public ClosureEnumerator(ClassType origThis, IEnumerable<string> args)
{
this.args = args;
this.origianlThis = origThis;
}
public IEnumerator<string> GetEnumerator()
{
return new Closure(origThis, args);
}
}
class Closure : IEnumerator<string>
{
public Closure(ClassType originalThis, IEnumerable<string> args)
{
state = 0;
this.args = args;
this.originalThis = originalThis;
}
private IEnumerable<string> args;
private IEnumerator<string> enumerator2;
private IEnumerator<string> argEnumerator;
//- Here ClassType is the type of the object that contained the method
// This may be optimized away if the method does not access any
// class members
private ClassType originalThis;
//This holds the state value.
private int state;
//The current value to return
private string currentValue;
public string Current
{
get
{
return currentValue;
}
}
}
El cuerpo del método se mueve entonces a partir del método original, a un método dentro de "cierre" llamada MoveNext, que devuelve un bool e implementa IEnumerable.MoveNext. Cualquier acceso a cualquier locals se enruta a través de "esto", y cualquier acceso a cualquier miembro de la clase se enruta a través de this.originalThis.
Cualquier "rendimiento de retorno expr" se traduce en:
currentValue = expr;
state = //the state number of the yield statement;
return true;
Cualquier sentencia break rendimiento se traduce en:
state = -1;
return false;
Hay una instrucción yield break "implícita" al final de la función. A continuación, se introduce una instrucción de conmutación al comienzo del procedimiento que examina el número de estado y salta a la etiqueta asociada.
se traduce entonces el método original en algo como esto:
IEnumerator<string> strings(IEnumerable<string> args)
{
return new ClosureEnumerable(this,args);
}
El hecho de que el estado del método es todo lo introduce en un objeto y que el método MoveNext utiliza una sentencia switch/variable de estado es lo permite que el iterador se comporte como si el control se devolviera al punto inmediatamente posterior a la última declaración de "rendimiento devuelto" la próxima vez que se llame a "MoveNext".
Es importante señalar, sin embargo, que la transformación utilizada por el compilador de C# no es la mejor manera de hacerlo. Su rendimiento es deficiente cuando intenta usar "rendimiento" con algoritmos recursivos. Hay un buen papel que esboza una mejor manera de hacer esto aquí:
http://research.microsoft.com/en-us/projects/specsharp/iterators.pdf
Vale la pena una lectura si no ha leído todavía.
Wow. Respuesta excelente y completa. Ojalá pudiera votar esto más de una vez. –