Pregunta

Me encantaría descubrirlo por mí mismo, pero me preguntaba ¿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 G # enciende esto?

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.

¿Fue útil?

Solución

El ejemplo de código en particular que estás viendo involucra 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. Sin embargo, la idea es la misma.

La primera transformación es la " foreach " transformación, 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 todas las declaraciones de rendimiento en el cuerpo de la función, asigna un número a cada una (un valor de estado) y crea una " goto label " Justo después del rendimiento.

La tercera transformación eleva todas las variables locales y argumentos de función en el cuerpo del método en un objeto llamado cierre.

Dado el código en tu ejemplo, 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 luego se mueve del método original a un método dentro de " Cierre " se llama MoveNext, que devuelve un bool e implementa IEnumerable.MoveNext. Cualquier acceso a cualquier local se enruta a través de " this " ;, y cualquier acceso a cualquier miembro de la clase se enruta a través de este.originalThis.

Cualquier " rendimiento retorno expr " está traducido a:

currentValue = expr;
state = //the state number of the yield statement;
return true;

Cualquier declaración de interrupción de rendimiento se traduce en:

state = -1;
return false;

Hay un " implícito " Declaración de pausa de rendimiento al final de la función. Luego se introduce una declaración de cambio al comienzo del procedimiento que mira el número de estado y salta a la etiqueta asociada.

El método original se traduce en algo como esto:

IEnumerator<string> strings(IEnumerable<string> args)
{
   return new ClosureEnumerable(this,args);
}

El hecho de que el estado del método se inserta en un objeto y que el método MoveNext utiliza una variable de estado / conmutación es lo que permite que el iterador se comporte como si el control se pasara al punto inmediatamente después del último " rendimiento de rendimiento " declaración la próxima vez que " MoveNext " se llama.

Sin embargo, es importante señalar que la transformación utilizada por el compilador de C # no es la mejor manera de hacerlo. Su desempeño es deficiente cuando se intenta utilizar " rendimiento " Con algoritmos recursivos. Hay un buen documento que describe una mejor manera de hacer esto aquí:

http://research.microsoft.com/en-us /projects/specsharp/iterators.pdf

Vale la pena leerlo si todavía no lo has leído.

Otros consejos

Acabo de ver esta pregunta: I escribí un artículo sobre él recientemente. Aunque tendré que agregar los otros enlaces mencionados aquí al artículo ...

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

(editado para apuntar a la parte 1 de la serie, no a la parte 4)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top