2010-12-15 113 views
6

Ok, comme je farfouillé avec la construction d'un recenseur personnalisé, j'avais remarqué ce comportement qui concerne le rendementLes merveilles du mot-clé yield

Supposons que vous avez quelque chose comme ceci:

public class EnumeratorExample 
    { 

     public static IEnumerable<int> GetSource(int startPoint) 
     { 
       int[] values = new int[]{1,2,3,4,5,6,7}; 
       Contract.Invariant(startPoint < values.Length); 
       bool keepSearching = true; 
       int index = startPoint; 

       while(keepSearching) 
       { 
         yield return values[index]; 
         //The mind reels here 
         index ++ 
         keepSearching = index < values.Length; 
       } 
     } 

    } 

Qu'est-ce qui rend possible sous le capot du compilateur d'exécuter l'index ++ et le reste du code dans la boucle while après avoir techniquement fait un retour de la fonction?

Répondre

9

Le compilateur réécrit le code dans un automate fini. La méthode unique que vous avez écrite est divisée en différentes parties. Chaque fois que vous appelez MoveNext (implicitement ou explicitement), l'état est avancé et le bloc de code correct est exécuté.

Suggestions de lecture si vous voulez en savoir plus de détails:

+0

Oui, d'accord, machine d'état, c'est ce que j'ai lu. Mais quel genre de code génère-t-il et que fait cette machine d'état? Pseudo code serait grandement apprécié. – dexter

+0

@Max Malygin: L'article que j'ai lié à http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx montre le code qui est généré. –

+0

@Mark, cool, merci, je vais vérifier ceux-là! – dexter

2

Yield est magique.

Eh bien, pas vraiment. Le compilateur génère une classe complète pour générer l'énumération que vous faites. C'est essentiellement du sucre pour vous simplifier la vie.

Lire this pour une intro.

EDIT: Mauvais. Lien changé, vérifiez à nouveau si vous avez une fois.

+0

merci, cela aide un peu, donc + – dexter

+0

@Max - Selon le moment où vous avez cliqué sur le lien, il peut être différent maintenant. J'ai d'abord posté le mauvais. – Donnie

4

Le compilateur génère une machine d'état en votre nom.

De la spécification du langage:

10.14 itérateurs

10.14.4 objets énumérateur

Quand un élément de fonction renvoyant un type d'interface énumérateur est mis en oeuvre au moyen d'un bloc itérateur, invoquant le membre de la fonction ne exécuter immédiatement le code dans le bloc d'itération. Au lieu de cela, un objet énumérateur est créé et renvoyé. Cet objet encapsule le code spécifié dans le bloc itérateur, et l'exécution du code dans le bloc itérateur se produit lorsque l'objet énumérateur de méthode MoveNext est invoquée.Un objet recenseur a les caractéristiques suivantes:

• Il met en œuvre IEnumerator et IEnumerator, où T est le type de rendement de l'itérateur.

• Implémente System.IDisposable.

• Il est initialisé avec une copie des valeurs d'argument (le cas échéant) et l'instance valeur transmise au membre de fonction.

• Il a quatre états potentiels, avant, en cours d'exécution, suspendu et après, et est initialement dans l'état avant.

Un objet énumérateur est typiquement un instance d'une classe de compilateur généré énumérateur qui encapsule le code dans le bloc itérateur et implémente les interfaces énumérateur, mais d'autres méthodes de mise en œuvre sont possibles. Si une classe recenseur est généré par le compilateur, cette classe sera imbriquée, directement ou indirectement, dans la classe contenant l'organe de fonction, il aura accès privé, et il ont un nom réservé à l'usage du compilateur (§2.4.2).

Pour avoir une idée de cela, voici comment réflecteur décompile votre classe:

public class EnumeratorExample 
{ 
    // Methods 
    public static IEnumerable<int> GetSource(int startPoint) 
    { 
     return new <GetSource>d__0(-2) { <>3__startPoint = startPoint }; 
    } 

    // Nested Types 
    [CompilerGenerated] 
    private sealed class <GetSource>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable 
    { 
     // Fields 
     private int <>1__state; 
     private int <>2__current; 
     public int <>3__startPoint; 
     private int <>l__initialThreadId; 
     public int <index>5__3; 
     public bool <keepSearching>5__2; 
     public int[] <values>5__1; 
     public int startPoint; 

     // Methods 
     [DebuggerHidden] 
     public <GetSource>d__0(int <>1__state) 
     { 
      this.<>1__state = <>1__state; 
      this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId; 
     } 

     private bool MoveNext() 
     { 
      switch (this.<>1__state) 
      { 
       case 0: 
        this.<>1__state = -1; 
        this.<values>5__1 = new int[] { 1, 2, 3, 4, 5, 6, 7 }; 
        this.<keepSearching>5__2 = true; 
        this.<index>5__3 = this.startPoint; 
        while (this.<keepSearching>5__2) 
        { 
         this.<>2__current = this.<values>5__1[this.<index>5__3]; 
         this.<>1__state = 1; 
         return true; 
        Label_0073: 
         this.<>1__state = -1; 
         this.<index>5__3++; 
         this.<keepSearching>5__2 = this.<index>5__3 < this.<values>5__1.Length; 
        } 
        break; 

       case 1: 
        goto Label_0073; 
      } 
      return false; 
     } 

     [DebuggerHidden] 
     IEnumerator<int> IEnumerable<int>.GetEnumerator() 
     { 
      EnumeratorExample.<GetSource>d__0 d__; 
      if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2)) 
      { 
       this.<>1__state = 0; 
       d__ = this; 
      } 
      else 
      { 
       d__ = new EnumeratorExample.<GetSource>d__0(0); 
      } 
      d__.startPoint = this.<>3__startPoint; 
      return d__; 
     } 

     [DebuggerHidden] 
     IEnumerator IEnumerable.GetEnumerator() 
     { 
      return this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator(); 
     } 

     [DebuggerHidden] 
     void IEnumerator.Reset() 
     { 
      throw new NotSupportedException(); 
     } 

     void IDisposable.Dispose() 
     { 
     } 

     // Properties 
     int IEnumerator<int>.Current 
     { 
      [DebuggerHidden] 
      get 
      { 
       return this.<>2__current; 
      } 
     } 

     object IEnumerator.Current 
     { 
      [DebuggerHidden] 
      get 
      { 
       return this.<>2__current; 
      } 
     } 
    } 
} 
2
+0

+1 J'ai raté la partie 4 :-(Bien repéré! –