2016-05-31 66 views
22

Stavo giocherellando in giro con yield e IEnumerable e ora sono curioso di sapere perchè e come funziona il seguente frammento:In che modo la resa è enumerabile?

public class FakeList : IEnumerable<int> 
{ 
    private int one; 
    private int two; 

    public IEnumerator<int> GetEnumerator() 
    { 
     yield return one; 
     yield return two; 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 

Ora, come fa il compilatore a girare questo:

public IEnumerator<int> GetEnumerator() 
{ 
    yield return one; 
    yield return two; 
} 

in un IEnumerator<int>?

+3

http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx –

+1

magia del compilatore! – RBarryYoung

risposta

27

Quando si utilizza yield return, il compilatore genera una classe di enumerazione per l'utente. Quindi il codice effettivo che viene utilizzato è molto più complesso di solo due dichiarazioni di ritorno. Il compilatore aggiunge tutto il codice necessario per restituire un enumeratore per te, che esegue l'iterazione dei risultati da yield return.


Questo è il codice generato dal FakeList.GetEnumerator():

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Runtime.CompilerServices; 

public class FakeList : IEnumerable<int>, IEnumerable 
{ 
    private int one; 
    private int two; 

    [IteratorStateMachine(typeof(<GetEnumerator>d__2))] 
    public IEnumerator<int> GetEnumerator() 
    { 
     yield return this.one; 
     yield return this.two; 
    } 

    IEnumerator IEnumerable.GetEnumerator() => 
     this.GetEnumerator(); 

    [CompilerGenerated] 
    private sealed class <GetEnumerator>d__2 : IEnumerator<int>, IDisposable, IEnumerator 
    { 
     private int <>1__state; 
     private int <>2__current; 
     public FakeList <>4__this; 

     [DebuggerHidden] 
     public <GetEnumerator>d__2(int <>1__state) 
     { 
      this.<>1__state = <>1__state; 
     } 

     private bool MoveNext() 
     { 
      switch (this.<>1__state) 
      { 
       case 0: 
        this.<>1__state = -1; 
        this.<>2__current = this.<>4__this.one; 
        this.<>1__state = 1; 
        return true; 

       case 1: 
        this.<>1__state = -1; 
        this.<>2__current = this.<>4__this.two; 
        this.<>1__state = 2; 
        return true; 

       case 2: 
        this.<>1__state = -1; 
        return false; 
      } 
      return false; 
     } 

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

     [DebuggerHidden] 
     void IDisposable.Dispose() 
     { 
     } 

     int IEnumerator<int>.Current => 
      this.<>2__current; 

     object IEnumerator.Current => 
      this.<>2__current; 
    } 
} 

Vedete la classe <GetEnumerator>d__2? Questo viene generato in base ai tuoi due yield return s.

12

Quando il compilatore vede yield return o yield break prende la funzione e ricopre la logica in una classe che implementa una macchina a stati. Un'istanza di questa classe viene quindi restituita quando viene chiamato il metodo.

C# In Depth ha una sezione su come appare il codice.

5

È un generatore. Non posso dire come la compilator lavora dietro, ma quando lo fai:

yield return x; 

Non lascerò la funzione di come il ritorno classico, invece si ritorna un valore e poi continuare l'esecuzione della funzione.

Se ricordo bene, c'è una piccola differenza tra un vero enumerabile e questo, se non si utilizza un metodo per trasformare il generatore in un vero enumerabile (a seconda di ciò che si sta tentando di realizzare) è possibile avere dei problemi

+0

Potresti espandere la differenza? Perché altri stanno mentonando che il compilatore genera un'implementazione enumerabile – Mafii

+0

proverò a trovarlo indietro. –

+0

hai trovato qualcosa? – Mafii