2009-10-28 9 views
5

Ho pensato al metodo IEnumerator.Reset(). Ho letto nella documentazione MSDN che esiste solo per l'interoperabilità COM. Come programmatore C++ mi sembra uno IEnumerator che supporta Reset è quello che chiamerei uno forward iterator, mentre uno IEnumerator che non supporta Reset è davvero un input iterator.Il C# trarrebbe vantaggio dalle distinzioni tra tipi di enumeratori, come gli iteratori C++?

Quindi una parte della mia domanda è, questa comprensione è corretta?

La seconda parte della mia domanda è, sarebbe di alcun beneficio in C# se ci fosse una distinzione tra iteratori di input e iteratori di inoltro (o "enumeratori" se si preferisce)? Non aiuterebbe a eliminare una certa confusione tra i programmatori, come quella trovata in questo SO question about cloning iterators?

MODIFICA: Chiarimento in avanti e iteratori di input. Un iteratore di input garantisce solo che è possibile enumerare i membri di una raccolta (o da una funzione generatore o un flusso di input) solo una volta. Questo è esattamente il modo in cui IEnumerator funziona in C#. Se è possibile enumerare o meno una seconda volta, viene determinato se è supportato o meno Reset. Un iteratore diretto, non ha questa restrizione. Puoi enumerare i membri tutte le volte che vuoi.

Alcuni programmatori C# non sottostimano il motivo per cui un IEnumerator non può essere utilizzato in modo affidabile in un algoritmo multipass. Si consideri il seguente caso:

void PrintContents(IEnumerator<int> xs) 
{ 
    while (iter.MoveNext()) 
    Console.WriteLine(iter.Current); 
    iter.Reset(); 
    while (iter.MoveNext()) 
    Console.WriteLine(iter.Current); 
} 

Se chiamiamo PrintContents in questo contesto, non è un problema:

List<int> ys = new List<int>() { 1, 2, 3 } 
PrintContents(ys.GetEnumerator()); 

Tuttavia guardare il seguente:

IEnumerable<int> GenerateInts() { 
    System.Random rnd = new System.Random(); 
    for (int i=0; i < 10; ++i) 
    yield return Rnd.Next(); 
} 

PrintContents(GenerateInts()); 

Se il IEnumerator supportato Reset, in altre parole supportavano algoritmi multi-pass, quindi ogni volta che si ripeteva la raccolta sarebbe diverso. Questo sarebbe indesiderabile, perché sarebbe un comportamento sorprendente. Questo esempio è un po 'simulato, ma si verifica nel mondo reale (ad esempio la lettura dai flussi di file).

+1

Penso che tu intenda "IEnumerator.Reset', non" IEnumerable .Reset ", giusto? –

+0

Sì, grazie! Mi dispiace per quello – cdiggins

+0

Interessante domanda. Ma forse dovresti spiegare il C++ - parla un po ', visto che molti programmatori C# vedranno questo. Potrebbe non essere ovvio quale sia un iteratore di input e un iteratore di inoltro, esattamente (in particolare, le capacità single/multipass di essi, che sono ciò che è veramente rilevante per questa domanda) – jalf

risposta

2

Interessante domanda. La mia opinione è che, naturalmente, C# sarebbe beneficio. Tuttavia, non sarebbe facile aggiungere.

La distinzione esiste in C++ a causa del suo sistema di tipi molto più flessibile. In C# non si dispone di un solido metodo generico per clonare gli oggetti, che è necessario per rappresentare gli iteratori in avanti (per supportare l'iterazione multi-pass). E, naturalmente, perché ciò sia davvero utile, devi anche supportare iteratori/enumeratori bidirezionali e ad accesso casuale. E per far sì che tutti funzionino senza problemi, hai davvero bisogno di una forma di digitazione, come i modelli C++.

In definitiva, gli ambiti dei due concetti sono diversi.

In C++, gli iteratori devono rappresentare tutto ciò che è necessario sapere su un intervallo di valori. Dato un paio di iteratori, non ho necessario il contenitore originale. Posso ordinare, posso cercare, posso manipolare e copiare elementi tanto quanto mi piace. Il contenitore originale è fuori dall'immagine.

In C#, gli enumeratori non sono pensati per fare altrettanto. In definitiva, sono solo progettati per farti scorrere attraverso la sequenza in modo lineare.

Per quanto riguarda Reset(), è ampiamente accettato che è stato un errore aggiungerlo in primo luogo. Se avesse funzionato e fosse stato implementato correttamente, allora sì, si potrebbe dire che il proprio enumeratore era analogo a inoltrare iteratori, ma in generale, è meglio ignorarlo come errore. E poi tutti gli enumeratori sono simili solo agli input iteratori.

Sfortunatamente.

+0

Dopo una tazza di caffè, non riesco a capire perché la clonazione sia necessaria per un iteratore multi-pass (ignorando i requisiti completi di un iteratore diretto) per ora. – cdiggins

+0

In quale altro modo si eseguiranno più passaggi su un iteratore diretto? Non è bidirezionale, quindi non puoi tornare al punto in cui ti trovavi e ripetere di nuovo. Devi creare una copia dell'iteratore, in modo da avere due iteratori che puntano alla stessa posizione nella sequenza e quindi possono essere incrementati individualmente. – jalf

+0

Se stai pensando che il metodo 'Reset()' potrebbe sostituire la clonazione, sì, una specie di. Tranne il reset, si ritorna solo ad un punto fisso nella sequenza (l'inizio). Ma un iteratore diretto dovrebbe essere in grado di eseguire più passaggi su un sottoinsieme di dati (ad esempio, solo gli ultimi tre elementi). Reset non aiuta davvero lì. (o almeno, sarebbe ridicolmente lento se dovessi fare un reset completo e attraversare l'intera sequenza solo per tornare al punto in cui si desidera eseguire l'attraversamento multi-passaggio) – jalf

3

Reset è stato un grande errore. Chiamo shenanigans su Reset. A mio parere, il modo corretto di riflettere la distinzione che si sta facendo tra "iteratori in avanti" e "input iteratori" nel sistema di tipo .NET è con la distinzione tra IEnumerable<T> e IEnumerator<T>.

Vedere anche this answer, dove Eric Lippert di Microsoft (in una capacità non ufficiale, senza dubbio, il mio punto è solo che è qualcuno con più credenziali di quello che devo affermare che si trattava di un errore di progettazione) fa un punto simile in Commenti. Vedi anche his awesome blog.

+1

+1 per i collegamenti. Tuttavia, non sono d'accordo con la tua analogia. IEnumerable è più analogo a un contenitore C++ http://www.sgi.com/tech/stl/Container.html – cdiggins

-1

Non credo. Chiamerei lo IEnumerable un iteratore in avanti e un iteratore di input. Non ti consente di tornare indietro o modificare la raccolta sottostante. Con l'aggiunta della parola chiave foreach, gli iteratori sono quasi un non-pensiero per la maggior parte del tempo.

Opinione: La differenza tra iteratori di ingresso (ottenere ogni uno) vs. iteratori di uscita (fare qualcosa per ognuno) è troppo banale per giustificare un aggiunta al quadro.Inoltre, per eseguire un iteratore di output, è necessario passare un delegato all'iteratore. L'iteratore di input sembra più naturale per i programmatori C#.

C'è anche IList<T> se il programmatore vuole un accesso casuale.

+0

In C++ un iteratore in avanti supporta algoritmi multi-pass, questo non è qualcosa che puoi fare in modo affidabile con un oggetto IEnumerable/IEnumerator. Ad esempio: se IEnumerable è stato generato da una funzione yield. – cdiggins

+0

Non capisco perché IEnumerable non supporta gli algoritmi multi-pass. Un iteratore personalizzato (utilizzando il rendimento restituito) crea un'istanza di IEnumerator. È possibile avere più oggetti IEnumerator che iterano sulla stessa raccolta contemporaneamente. –

+2

Ma dato solo un IEnumerator, non è possibile fare più di un passaggio sulla raccolta. È necessario avere accesso alla raccolta sottostante per eseguire l'iterazione multipass, che è un po 'un difetto. (Dal momento che in C++, gli iteratori sono progettati per coprire tutte le tue esigenze iterative .. In C#, alcuni algoritmi abbastanza basilari (qualsiasi cosa che richiede più di un passaggio) devono rompere l'astrazione e richiedere l'accesso al contenitore sottostante) – jalf

1

Venendo da C# prospettiva:

non userete mai IEnumerator direttamente. Di solito si fa una dichiarazione foreach, che si aspetta un IEnumerable.

IEnumerable _myCollection; 
... 
foreach (var item in _myCollection) { /* Do something */ } 

Non passa intorno IEnumerator neanche. Se si desidera passare una raccolta che richiede l'iterazione, si passa IEnumerable. Poiché IEnumerable ha una singola funzione, che restituisce un IEnumerator, può essere utilizzato per iterare la raccolta più volte (più passaggi).

Non c'è bisogno di una funzione Reset() su IEnumerator perché se si vuole ricominciare, basta buttare via quello vecchio (garbage collection) e ottenerne uno nuovo.

1

Il framework .NET potrebbe trarre enormi vantaggi se esistesse un modo per chiedere a IEnumerator<T> quali potenzialità supportare e quali promesse potrebbe fare. Tali funzionalità sarebbero anche utili in IEnumerable<T>, ma essere in grado di porre le domande di un enumeratore consentirebbe il codice che può ricevere un enumeratore da wrapper come ReadOnlyCollection per utilizzare la raccolta sottostante in modi migliori senza dover coinvolgere il wrapper.

Dato un qualsiasi enumeratore per una raccolta che è in grado di essere enumerato nella sua interezza e non è troppo grande, si potrebbe produrre da esso uno IEnumerable<T> che restituirebbe sempre la stessa sequenza di elementi (in particolare l'insieme di elementi rimanenti in l'enumeratore) leggendo l'intero contenuto su un array, smaltendo e scartando l'enumeratore e ricevendo un enumeratore dall'array (utilizzando quello al posto dell'enumeratore abbandonato originale), avvolgendo l'array in un ReadOnlyCollection<T> e restituendolo. Sebbene un simile approccio possa funzionare con qualsiasi tipo di collezione enumerabile che soddisfi i criteri sopra indicati, sarebbe orribilmente inefficiente con la maggior parte di essi. Avere un mezzo per chiedere a un enumeratore di fornire il suo contenuto rimanente in un valore immutabile IEnumerable<T> consentirebbe a molti tipi di enumeratori di eseguire l'azione indicata in modo molto più efficiente.

Problemi correlati