2008-09-25 12 views
29

Me encantaría resolverlo yo mismo, pero me preguntaba aproximadamente ¿cuál es el algoritmo para convertir una función con declaraciones de rendimiento en una máquina de estado para un enumerador? Por ejemplo ¿cómo C# girar esto:Algoritmo para implementar la declaración de rendimiento de C#

IEnumerator<string> strings(IEnumerable<string> args) 
{ IEnumerator<string> enumerator2 = getAnotherEnumerator();  
    foreach(var arg in arg) 
    { enumerator2.MoveNext(); 
     yield return arg+enumerator.Current; 
    } 
} 

en esto:

bool MoveNext() 
{ switch (this.state) 
    { 
     case 0: 
      this.state = -1; 
      this.enumerator2 = getAnotherEnumerator(); 
      this.argsEnumerator = this.args.GetEnumerator(); 
      this.state = 1; 
      while (this.argsEnumerator.MoveNext()) 
      { 
       this.arg = this.argsEnumerator.Current; 
       this.enumerator2.MoveNext(); 
       this.current = this.arg + this.enumerator2.Current; 
       this.state = 2; 
       return true; 

       state1: 
       this.state = 1; 
      } 
      this.state = -1; 
      if (this.argsEnumerator != null) this.argsEnumerator.Dispose(); 
      break; 

     case 2: 
      goto state1; 
    } 
    return false; 
} 

Por supuesto, el resultado puede ser completamente diferente dependiendo del código original.

Respuesta

58

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.

+2

Wow. Respuesta excelente y completa. Ojalá pudiera votar esto más de una vez. –

7

Raymond chen responde a esto; http://blogs.msdn.com/b/oldnewthing/archive/2008/08/12/8849519.aspx

(editado para que apunte a la parte 1 de la serie, que no forma parte 4)

+1

Raymond Chen solo dice que "es mágico" –

+0

Creo que la marxidad quiere descubrir cuál es el algoritmo utilizado por el compilador para interpretar y transformar el código de C# del iterador bloquea en el IL que corresponde a una máquina de estado. –

+0

sí Romain es correcto –

7

Acabo de ver esta pregunta - I wrote an article recientemente. Tendré que agregar los otros enlaces mencionados aquí al artículo ...

Cuestiones relacionadas