2012-07-25 13 views
12

In C#, l'interfaccia IEnumerator definisce un modo per attraversare una raccolta e osservare gli elementi. Penso che questo sia tremendamente utile perché se si passa a IEnumerable<T> a un metodo, non si modifica la fonte originale.Perché Iterator definisce l'operazione remove()?

Tuttavia, in Java, Iterator definisce l'operazione remove su (facoltativamente!) Consentire l'eliminazione di elementi. Non vi è alcun vantaggio nel passare Iterable<T> a un metodo poiché tale metodo può ancora modificare la raccolta originale.

remove s' optionalness è un esempio del refused bequest odore, ma ignorando che (già discusso here) Sarei interessato alle decisioni di progettazione che hanno indotto un evento remove da attuare sull'interfaccia.

Quali sono le decisioni di progettazione che hanno portato a remove aggiunto a Iterator?

Per mettere un altro modo, qual è la decisione di progettazione C# che non ha esplicitamente remove definito su IEnumerator?

risposta

8

Iterator è in grado di rimuovere elementi durante l'iterazione. Non è possibile ripetere la raccolta utilizzando l'iteratore e rimuovere elementi dalla raccolta di destinazione utilizzando il metodo remove() di tale raccolta. Otterrai ConcurrentModificationException alla prossima chiamata di Iterator.next() perché l'iteratore non può sapere come esattamente la collezione è stata cambiata e non può sapere come continuare ad iterare.

Quando si utilizza remove() di iteratore, sa come è stata modificata la raccolta. Inoltre in realtà non è possibile rimuovere alcun elemento della collezione ma solo quello attuale. Questo semplifica la continuazione dell'iterazione.

Per quanto riguarda i vantaggi di passare iteratore o Iterable: è sempre possibile utilizzare Collection.unmodifireableSet() o Collection.unmodifireableList() per impedire la modifica della raccolta.

+0

'unmodifiableXYZ' è solo una cosa runtime però, mi piacciono gli invarianti statici. Ero più interessato alla decisione di progettazione che ha portato alla rimozione di 'remove' e forse perché C# non ha scelto troppo. –

+0

La cosa peggiore da usare 'non modificabileXYZ' è che se qualcuno ha passato un' XYZ' già avvolto, puoi avvolgerlo due volte, e non hai modo di evitarlo a meno che non usi la reflection per risolverlo. Anche se ritengo che l'implementazione possa ottimizzarlo, mi sembra ancora troppo brutto. –

+0

Considerata l'implementazione di OpenJDK, sembra che non eviti il ​​wrapping multiplo. Ciò significa che in un'applicazione complicata potresti finalmente completare un semplice 'ArrayList ' per 100 o più volte. –

1

Ci sono situazioni in cui si desidera essere in grado di rimuovere elementi utilizzando l'iteratore perché è il modo più efficiente per farlo. Ad esempio, quando si attraversa una struttura di dati collegata (ad esempio un elenco collegato), la rimozione mediante l'iteratore è un'operazione O(1) ... rispetto a O(N) tramite le operazioni List.remove().

Naturalmente, molti collezioni sono progettati in modo che modificando la raccolta durante una collezione con qualsiasi altro mezzo di Iterator.remove() si tradurrà in una ConcurrentModificationException.


Se si dispone di una situazione in cui non si desidera consentire la modifica tramite un iteratore collezione, avvolgendolo con Collection.unmodifiableXxxx e usando il suo iteratore avrà l'effetto desiderato. In alternativa, penso che Apache Commons fornisce un semplice wrapper iteratore immodificabile.


proposito IEnumerable soffre dal medesimo "odore" come Iterator. Dai uno sguardo al metodo reset(). Ero anche curioso di sapere come la classe C# LinkedList si occupa del problema di rimozione O(N). Sembra che lo faccia per esponendo le parti interne della lista ...nella forma delle proprietà First e Last i cui valori sono LinkedListNode riferimenti. Ciò viola un altro principio di progettazione ... ed è (IMO) molto più pericoloso di Iterator.remove().

+0

Mi rendo conto che 'reset' è lo stesso odore, ma (come ho provato a dire nella domanda), non mi interessa molto. 'reset' non consente a nessuno di cancellare il contenuto del contenitore. Il mio principale punto di contesa è che l'iterabile di C# lascerà la raccolta nello stesso modo, indipendentemente dal metodo utilizzato (ignorando la riflessione). –

+0

@JeffFoster - come ho detto, gli iteratori Java non forniscono questa garanzia ... ma se lo vuoi, ci sono modi semplici ed economici per raggiungerlo. –

+0

sì, voglio solo arrivare al motivo per cui non forniscono tale garanzia. Quale insieme di compromessi ha fatto Java/C# a livello di progettazione della libreria per supportare (o non supportare) 'remove'. –

2

È probabilmente dovuto al fatto che la rimozione di elementi da una raccolta mentre si esegue l'iterazione è sempre stata una causa di errori e comportamento strano. Dalla lettura della documentazione suggerirebbe che Java applica in fase di esecuzione remove() viene chiamato solo una volta per chiamata a next(), il che mi fa pensare che sia stato appena aggiunto per impedire alle persone di rovinare la rimozione dei dati da un elenco durante l'iterazione.

-1

Questa è in realtà una funzionalità eccezionale di Java. Come forse sapete, quando si scorre un elenco in .NET per rimuovere elementi (di cui esistono diversi casi d'uso), si hanno solo due opzioni.

var listToRemove = new List<T>(originalList); 
foreach (var item in originalList) 
{ 
    ... 
    if (...) 
    { 
     listToRemove.Add(item) 
    } 
    ... 
} 

foreach (var item in listToRemove) 
{ 
    originalList.Remove(item); 
} 

o

var iterationList = new List<T>(originalList); 
for (int i = 0; i < iterationList.Count; i++) 
{ 
    ... 
    if (...) 
    { 
     originalList.RemoveAt(i); 
    } 
    ... 
} 

Ora, io preferisco la seconda, ma con Java non ho bisogno tutto questo perché mentre io sono su un elemento che posso toglierlo e tuttavia la l'iterazione continuerà! Onestamente, anche se può sembrare fuori luogo, è davvero un'ottimizzazione in molti modi.

+0

Al contrario, lo vedo come ripugnante. Mi impedisce di passare un insieme di elementi a un metodo senza sapere se il metodo tosterà il mio contenitore :) Sono interessato alla decisione di progettazione che l'ha portato a essere aggiunto, e in particolare perché non è in C#. –

+0

Segui il post di @ StephenC per impedirgli di "tostare il contenitore", ma penso che tu stia facendo questa domanda nel posto sbagliato se stai cercando di scoprire perché i designer Java l'hanno implementato e perché Microsoft non l'ha fatto. Sai cosa intendo? Ma considera quanto sopra e pensa a quanto sia più efficiente. Inoltre, questo è un metodo che controlli o è uno di quelli che stai consumando? –

+0

Il tuo esempio è completamente sbagliato.Sia C# che Java forniscono il metodo 'RemoveAt' per' List '. Quindi il tuo stile di codice Java funziona anche per C#. Se il tuo codice C# usa la struttura 'foreach' dovresti usare' for (:) 'anche in Java. E vedrai che non puoi in realtà chiamare il metodo 'remove' in tale ciclo (dato che non hai un riferimento esplicito a' Iterator'), così il tuo "beneficio" di Java se n'è andato. –