Question

J'aimerais bien comprendre cela moi-même, mais je me demandais quel est l'algorithme de conversion d'une fonction avec des déclarations de rendement en une machine à états pour un énumérateur? Par exemple, comment C # transforme-t-il ceci:

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

dans ceci:

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;
}

Bien sûr, le résultat peut être complètement différent en fonction du code d'origine.

Était-ce utile?

La solution

L'échantillon de code que vous examinez implique une série de transformations. Veuillez noter qu'il s'agit d'une description approximative de l'algorithme. Les noms réels utilisés par le compilateur et le code exact qu'il génère peuvent être différents. L’idée est la même, cependant.

La première transformation est le "foreach". transformation, qui transforme ce code:

foreach (var x in y)
{
   //body
}

dans ce code:

var enumerator = y.GetEnumerator();
while (enumerator.MoveNext())
{
    var x = enumerator.Current;
    //body
}

if (y != null)
{
    enumerator.Dispose();
}

La deuxième transformation trouve toutes les instructions de rendement dans le corps de la fonction, attribue un numéro à chacune (une valeur d'état) et crée un "goto label". juste après le rendement.

La troisième transformation élève toutes les variables locales et les arguments de fonction du corps de la méthode dans un objet appelé fermeture.

Étant donné le code de votre exemple, cela ressemblerait à ceci:

 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;
        }
    }
}

Le corps de la méthode est ensuite déplacé de la méthode d'origine vers une méthode à l'intérieur de "Closure". appelé MoveNext, qui retourne un bool et implémente IEnumerable.MoveNext. Tout accès à une section locale est acheminé via "this", et tout accès à un membre du groupe est acheminé via this.originalThis.

Tout " return return expr " se traduit en:

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

Toute déclaration de rupture de rendement est traduite en:

state = -1;
return false;

Il existe un " implicite " déclaration de rupture de rendement à la fin de la fonction. Une instruction switch est ensuite introduite au début de la procédure. Elle examine le numéro d'état et passe à l'étiquette associée.

La méthode d'origine est ensuite traduite en quelque chose comme ceci:

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

Le fait que l'état de la méthode soit entièrement poussé dans un objet et que la méthode MoveNext utilise une variable switch / state est ce qui permet à l'itérateur de se comporter comme si le contrôle était renvoyé au point immédiatement après la dernière " return return " déclaration la prochaine fois " MoveNext " s'appelle.

Il est toutefois important de souligner que la transformation utilisée par le compilateur C # n’est pas la meilleure façon de procéder. Il présente des performances médiocres lorsqu’on essaie d’utiliser "rendement". avec des algorithmes récursifs. Il existe un bon document décrivant une meilleure façon de procéder:

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

Cela vaut la peine d'être lu si vous ne l'avez pas encore lu.

Autres conseils

Vous venez de repérer cette question - je écrit un article sur celui-ci. Il faudra que je rajoute les autres liens mentionnés ici à l'article ...

Raymond chen répond à cela; http://blogs.msdn.com/b/ oldnewthing / archive / 2008/08/12 / 8849519.aspx

(modifié pour pointer sur la partie 1 de la série, pas la partie 4)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top