2014-09-09 4 views
21

IEnumerator contiene MoveNext(), Reset() e Current come membri. Supponiamo ora di aver spostato questi metodi e proprietà nell'interfaccia IEnumerable e rimosso il metodo GetEnumerator() e l'interfaccia IEnumerator.Qual è il motivo della creazione di IEnumerator?

Ora, l'oggetto della classe che implementa IEnumerable sarà in grado di accedere ai metodi e alla proprietà e quindi può essere iterato su.

  • Perché l'approccio di cui sopra non è seguito ei problemi che affronterò se lo seguo?
  • In che modo la presenza dell'interfaccia IEnumerator risolve questi problemi?
+0

pensiero rapido; è usato principalmente per il ciclo foreach - roba del compilatore !! – codebased

+6

GetEnumerator restituisce un nuovo enumeratore in modo che più enumeratori possano navigare indipendentemente attraverso una raccolta. Con la tua soluzione non è possibile. – Jehof

+15

@flindeberg Non c'è tag tag. Non sta facendo nulla in modo improprio. – Servy

risposta

48

Un iteratore contiene stato separato alla collezione: contiene un cursore per dove siete all'interno la collezione. Come tale, ci deve essere un oggetto separato per rappresentare quello stato aggiunto, un modo per ottenere che l'oggetto, e le operazioni su quell'oggetto - quindi IEnumerator (e IEnumerator<T>), GetEnumerator(), ei membri iteratore.

Immaginate se non abbiamo fatto hanno la stato separato, e poi abbiamo scritto:

var list = new List<int> { 1, 2, 3 }; 

foreach (var x in list) 
{ 
    foreach (var y in list) 
    { 
     Console.WriteLine("{0} {1}", x, y); 
    } 
} 

Che dovrebbe stampa "1 1", "1 2", "1 3", " 2 1 "ecc ... ma senza uno stato extra, come potrebbe" conoscere "le due diverse posizioni dei due anelli?

25

Supponiamo ora di aver spostato questi metodi e proprietà nell'interfaccia IEnumerable e rimosso il metodo GetEnumerator() e l'interfaccia IEnumerator.

Tale progettazione eviterebbe l'enumerazione simultanea alla raccolta. Se è stata la collezione stessa che rintracciato la posizione corrente, non si poteva avere diversi thread enumerazione della stessa collezione, o enumerazioni anche nidificati, come questo:

foreach (var x in collection) 
{ 
    foreach (var y in collection) 
    { 
     Console.WriteLine("{0} {1}", x, y); 
    } 
} 

Delegando la responsabilità di tracciare la posizione corrente ad un oggetto diverso (l'enumeratore), rende ogni enumerazione della raccolta indipendente dalle altre

+33

Sei stato skeeted di 6 secondi. – Mixxiphoid

2

La logica di iterazione (foreach) non è associata a IEnumerables o IEnumerator. Quello di cui hai bisogno per lavorare è un metodo chiamato GetEnumerator nella classe che restituisce un oggetto di classe che ha metodi MoveNext(), Reset() e la proprietà Current. Ad esempio il seguente codice funziona e creerà un ciclo infinito.

In una prospettiva di progettazione, la separazione serve a garantire che il contenitore (IEnumerable) non mantenga alcun stato durante e dopo il completamento delle operazioni di iterazione (foreach).

public class Iterator 
    { 
     public bool MoveNext() 
     { 
      return true; 
     } 

     public void Reset() 
     { 

     } 

     public object Current { get; private set; } 
    } 

    public class Tester 
    { 
     public Iterator GetEnumerator() 
     { 
      return new Iterator(); 
     } 

     public static void Loop() 
     { 
      Tester tester = new Tester(); 
      foreach (var v in tester) 
      { 
       Console.WriteLine(v); 
      } 

     } 

    } 
8

Un po 'di una risposta lunga, le due risposte precedenti coprono la maggior parte di essa, ma ho trovato alcuni aspetti che ho trovato interessante quando guardando foreach nella specifica C# lingua. A meno che tu non sia interessato a questo, smetti di leggere.

Ora verso la parte intering, secondo C# espansione specifiche delle seguenti affermazioni:

foreach (V v in x) embedded-statement 

gves voi:

{` 
E e = ((C)(x)).GetEnumerator(); 
try { 
    while (e.MoveNext()) { 
     V v = (V)(T)e.Current; 
     embedded-statement 
    } 
} 
finally { 
    … // Dispose e 
} 
} 

Avere un qualche tipo di funzione identità che segue x == ((C)(x)).GetEnumerator() (è è il proprio enumeratore) e l'uso dei loop di @ JonSkeet produce qualcosa del genere (rimosso try/catch per brevità):

var list = new List<int> { 1, 2, 3 }; 

while (list.MoveNext()) { 
int x = list.Current; // x is always 1 
while (list.MoveNext()) { 
    int y = list.Current; // y becomes 2, then 3 
    Console.WriteLine("{0} {1}", x, y); 
} 
} 

stamperà qualcosa sulla falsariga di:

1 2 
1 3 

E poi list.MoveNext() tornerà false sempre. Il che rende un punto molto importante, se si guarda a questo:

var list = new List<int> { 1, 2, 3 }; 

// loop 
foreach (var x in list) Console.WriteLine(x); // Will print 1,2,3 
// loop again 
// Will never enter loop, since reset wasn't called and MoveNext() returns false 
foreach (var y in list) Console.WriteLine(x); // Print nothing 

Quindi, con il suddetto in mente, e notare che è assolutamente fattibile in quanto la dichiarazione foreach cerca un GetEnumerator() -Metodo prima di controllare se o no il tipo implementa IEnumerable<T>:

Perché è stato l'approccio di cui sopra non ha seguito ed i problemi che si troveranno ad affrontare se lo seguo?

Non puoi nido loop, né è possibile utilizzare l'istruzione foreach di accedere alla stessa collezione più di una volta senza chiamare Reset() manualmente tra di loro. Inoltre cosa succede quando smaltiamo l'enumeratore dopo ogni foreach?

In che modo la presenza dell'interfaccia IEnumerator risolve questi problemi?

Tutte le iterazioni sono indipendenti l'una dall'altra, sia che si tratti di nidificazione, più thread ecc., L'enumerazione è separata dalla raccolta stessa. Lo si può considerare un po 'come separations of concerns, or SoC, poiché l'idea è quella di separare l'attraversamento dalla lista stessa e che l'attraversamento in nessun caso dovrebbe alterare lo stato della raccolta. IE una chiamata a MoveNext() con il tuo esempio potrebbe modificare la raccolta.

3

Altri hanno già risposto alla tua domanda, ma voglio aggiungere un altro piccolo dettaglio.

Decompiliamo System.Collection.Generics.List(Of T). La sua definizione è simile al seguente:

public class List<T> : IList<T>, ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable 

e la sua definizione enumeratore si presenta così:

public struct Enumerator : IEnumerator<T>, IDisposable, IEnumerator 

Come si può vedere, Lista per sé è una classe, ma la sua Enumerator è una struct, e questo design helps boost performance. Supponiamo di non avere una separazione tra IEnumerable e IEnumerator.In questa situazione sei costretto a fare List una struct, ma this is not a very good idea, quindi non puoi farlo. Pertanto, stai perdendo una buona opportunità per ottenere un incremento delle prestazioni.

Con la separazione tra IEnumerable e IEnumerator è possibile implementare ciascuna interfaccia come desiderato e utilizzare struct per gli enumeratori.

+0

In che modo Enumerator è una struttura che aiuta a migliorare le prestazioni e cosa succede se si tratta di una classe? – zizouraj

+0

@zizouraj: le strutture vengono passate per valore, allocate nello stack e non devono essere raccolte dal GC (anche se in realtà si tratta di un dettaglio di implementazione, ma importante per le prestazioni). Nota che nel 99% dei casi tu ** non dovresti usare le structs ** (specialmente quelle mutabili), e che il team BCL si è concesso questa microottimizzazione solo perché questa è una collezione core destinata ad essere usata molto. – Groo

+0

@Groo: l'utilizzo di una struttura per implementare 'IEnumerator ' non migliorerà le prestazioni nei casi in cui il codice utilizza 'IEnumerable .GetEnumerator()'. Sia C# che VB.NET guardano per vedere se una collezione ha un metodo chiamato 'GetEnumerator' che restituisce qualcosa che ha un metodo' MoveNext' e una proprietà 'Current'; 'Lista ' ha un tale metodo, separato dalla sua implementazione di 'IEnumerable .GetEnumerator'. È troppo brutto VB.NET e C# richiedono che il metodo sia denominato 'GetEnumerator', altrimenti il ​​comportamento ideale sarebbe stato avere' GetEnumerator' restituire una classe, ma il metodo utilizzato da ... – supercat