2012-07-25 20 views
12

W języku C# interfejs IEnumerator definiuje sposób przechodzenia przez kolekcję i przeglądania elementów. Myślę, że jest to niezwykle użyteczne, ponieważ jeśli podasz IEnumerable<T> dla metody, to nie zmodyfikujesz oryginalnego źródła.Dlaczego Iterator definiuje operację remove()?

Jednak w Javie Iterator definiuje operację remove na (opcjonalnie!) Pozwalającą usuwać elementy. Nie ma żadnej korzyści przy przekazywaniu do metody Iterable<T>, ponieważ ta metoda może nadal modyfikować oryginalną kolekcję.

remove „s optionalness jest przykładem refused bequest zapachu, ale nie zważając, że (już omówione here) Byłbym zainteresowany decyzji projektowych, które wywołało zdarzenie remove być realizowane w interfejsie.

Jakie są decyzje projektowe, które doprowadziły do ​​dodania remove do Iterator?

Innymi słowy, jaka jest decyzja projektowa C#, która jawnie nie ma remove zdefiniowanej na IEnumerator?

Odpowiedz

8

Iterator potrafi usuwać elementy podczas iteracji. Nie można iterować kolekcji za pomocą iteratora i usuwać elementów z kolekcji docelowej przy użyciu metody tej kolekcji. Otrzymasz ConcurrentModificationException przy następnym wywołaniu Iterator.next(), ponieważ iterator nie może wiedzieć, jak dokładnie kolekcja została zmieniona i nie może wiedzieć, jak kontynuować iterację.

Podczas korzystania z Iteratora remove() wie, w jaki sposób zmieniono kolekcję. Co więcej, nie można usunąć żadnego elementu kolekcji, ale tylko bieżącego. Upraszcza to kontynuację iteracji.

Jeśli chodzi o zalety przekazywania iteratora lub iteratora: zawsze można użyć Collection.unmodifireableSet() lub Collection.unmodifireableList(), aby uniemożliwić modyfikację kolekcji.

+0

'Niezmodyfikowalny XYZ' jest jednak tylko runtime, lubię statyczne niezmienniki. Byłem bardziej zainteresowany decyzją projektową, która doprowadziła do dodania 'remove' i być może dlatego C# nie wybrał zbyt. –

+0

Najgorsze w użyciu 'unmodifiableXYZ' jest to, że jeśli ktoś przekazał' XYZ', który jest już zapakowany, możesz owijać go dwukrotnie, i nie masz sposobu, aby tego uniknąć, chyba że użyjesz refleksji, aby to rozpracować. Chociaż uważam, że wdrożenie to zoptymalizuje, nadal jest dla mnie zbyt złe. –

+0

Patrząc na implementację OpenJDK, nie wydaje się, aby uniknąć wielokrotnego zawijania. Oznacza to, że w skomplikowanej aplikacji możesz wreszcie owinąć prostą 'ArrayList ' przez 100 lub więcej razy. –

1

Istnieją sytuacje, w których chcesz móc usuwać elementy za pomocą iteratora, ponieważ jest to najbardziej efektywny sposób. Na przykład podczas przechodzenia połączonej struktury danych (na przykład listy połączonej) usuwanie przy użyciu iteratora jest operacją O(1) ... w porównaniu z O(N) przy użyciu operacji List.remove().

I oczywiście wiele kolekcje są zaprojektowane w taki sposób, że podczas modyfikowania kolekcji kolekcji za pomocą innych środków niż Iterator.remove() spowoduje ConcurrentModificationException.


Jeśli masz sytuację, w której nie chcesz, aby umożliwić modyfikację poprzez iterator zbiórki, owijając go za pomocą Collection.unmodifiableXxxx i stosując to iterator będzie miała pożądany efekt. Alternatywnie, myślę, że Apache Commons zapewnia prostą, niemodyfikowalną owijkę iteratora.


Nawiasem mówiąc IEnumerable cierpi z tego samego "zapach" jako Iterator. Spójrz na metodę reset(). Byłem także ciekawy, jak klasa C# LinkedList radzi sobie z problemem usuwania O(N). Wygląda na to, że robi to przez odsłaniając elementy wewnętrzne listy ...w postaci właściwości o wartościach i Last, których wartościami są odniesienia LinkedListNode. To narusza inną zasadę projektowania ... i jest (IMO) o wiele bardziej niebezpieczne niż Iterator.remove().

+0

Zdaję sobie sprawę, że "reset" jest tym samym zapachem, ale (jak próbowałem określić w pytaniu), nie jestem tym zainteresowany. 'reset' nie pozwala nikomu na usunięcie zawartości kontenera. Moim głównym punktem spornym jest to, że C# 'iterable opuści kolekcję w ten sam sposób, bez względu na to, jaką metodę z nią zrobi (pomijając refleksję). –

+0

@JeffFoster - jak już powiedziałem, iteratory Java nie zapewniają tej gwarancji ... ale jeśli chcesz, są proste i niedrogie sposoby, aby to osiągnąć. –

+0

tak, chcę tylko dowiedzieć się, dlaczego nie zapewniają tej gwarancji. Jaki zestaw kompromisów zrobił Java/C# na poziomie projektowania biblioteki, aby obsługiwać (lub nie obsługiwać) 'remove'. –

2

Jest to prawdopodobnie spowodowane faktem, że usuwanie elementów z kolekcji podczas iteracji nad nią zawsze było przyczyną błędów i dziwnego zachowania. Od przeczytania dokumentacji sugerowałaby, że Java wymusza w czasie wykonywania remove() jest wywoływana tylko raz na każde wywołanie next(), co powoduje, że myślę, że został właśnie dodany, aby zapobiec zakłóceniom podczas usuwania iteracji z listy podczas iteracji.

-1

To jest rzeczywiście niesamowita cecha Java. Jak dobrze wiesz, podczas iteracji listy w .NET w celu usunięcia elementów (z których jest kilka przypadków użycia) masz tylko dwie opcje.

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

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

lub

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

Teraz wolę drugi, ale z Java nie muszę wszystko to dlatego, że gdy jestem na elemencie mogę go usunąć i jeszcze iteracja będzie kontynuowana! Szczerze mówiąc, choć może się wydawać nie na miejscu, jest to naprawdę optymalizacja na wiele sposobów.

+0

Przeciwnie, uważam to za odrażające. To uniemożliwia mi przekazanie zestawu elementów do metody, nie wiedząc, czy metoda wzniesie toast za mój pojemnik :) Interesuje mnie decyzja projektowa, która doprowadziła do jej dodania, a zwłaszcza dlaczego nie jest w języku C#. –

+0

Śledź posty @ StephenC, aby zachować je z "opiekania pojemnika" - ale myślę, że zadajesz to pytanie w niewłaściwym miejscu, jeśli próbujesz dowiedzieć się, dlaczego projektanci Javy go wdrożyli i dlaczego Microsoft tego nie zrobił. Wiesz co mam na myśli? Ale weź pod uwagę powyższe i zastanów się, o ile jest to bardziej efektywne. Czy jest to metoda, którą kontrolujesz, czy też jest to jedna z frameworka, które zużywasz? –

+0

Twój przykład jest całkowicie błędny.Zarówno C#, jak i Java udostępniają metodę 'RemoveAt' dla' Listy '. Twój styl kodu Java działa również dla języka C#. Jeśli twój kod C# używa struktury foreach, powinieneś użyć 'for (:)' również w Javie. Zobaczysz, że tak naprawdę nie możesz wywołać metody 'remove' w takiej pętli (ponieważ nie masz wyraźnego odniesienia do' Iteratora'), więc twoja "zaleta" Javy zniknęła. –