Niedawno natknąłem się na tę typową nieprawidłową operację Collection was modified
w języku C # i choć w pełni ją rozumiem, wydaje się, że jest to tak częsty problem (Google, około 300 000 wyników!). Ale wydaje się również logiczną i prostą rzeczą modyfikowanie listy podczas jej przeglądania.
List<Book> myBooks = new List<Book>();
public void RemoveAllBooks(){
foreach(Book book in myBooks){
RemoveBook(book);
}
}
RemoveBook(Book book){
if(myBooks.Contains(book)){
myBooks.Remove(book);
if(OnBookEvent != null)
OnBookEvent(this, new EventArgs("Removed"));
}
}
Niektóre osoby utworzą kolejną listę, aby ją powtarzać, ale to tylko unika problemu. Jakie jest prawdziwe rozwiązanie lub jaki jest tutaj rzeczywisty problem projektowy? Wydaje się, że wszyscy chcemy to zrobić, ale czy wskazuje to na błąd projektowy?
You consume an iterator from client code by using a For Each…Next (Visual Basic) or foreach (C#) statement
liniiIterator
(działa zgodnie z opisem) iListIterator
(działa, jak chcesz). Oznaczałoby to, że w celu zapewnienia funkcjonalności iteratora dla szerokiego zakresu typów i implementacji kolekcjiIterator
jest wyjątkowo restrykcyjny (co się stanie, jeśli usuniesz węzeł w zbalansowanym drzewie? Ma to więcejnext()
sensu),ListIterator
ponieważ ma większą moc, ponieważ ma mniej przypadków użycia.Odpowiedzi:
Krótka odpowiedź: nie
Mówiąc prosto, wywołujesz niezdefiniowane zachowanie, gdy iterujesz kolekcję i modyfikujesz ją jednocześnie. Pomyśl o usunięcie z
next
elementu w sekwencji. Co by się stało, gdybyMoveNext()
się nazywało?Źródło: MSDN
Nawiasem mówiąc, możesz skrócić
RemoveAllBooks
to po prostureturn new List<Book>()
Aby usunąć książkę, zalecamy zwrócenie kolekcji z filtrem:
return books.Where(x => x.Author != "Bob").ToList();
Możliwa implementacja na półce wyglądałaby następująco:
źródło
Utworzenie nowej listy w celu jej modyfikacji jest dobrym rozwiązaniem problemu, że istniejące iteratory listy nie mogą niezawodnie kontynuować po zmianie, chyba że wiedzą, jak zmieniła się lista.
Innym rozwiązaniem jest dokonanie modyfikacji za pomocą iteratora, a nie samego interfejsu listy. Działa to tylko wtedy, gdy może istnieć tylko jeden iterator - nie rozwiązuje problemu wielu wątków iterujących jednocześnie po liście, co (o ile dopuszczalny jest dostęp do nieaktualnych danych), powoduje utworzenie nowej listy.
Alternatywnym rozwiązaniem, które mogliby zastosować implementatorzy frameworku, jest śledzenie wszystkich iteratorów listy i powiadamianie ich o wystąpieniu zmian. Jednak narzuty związane z tym podejściem są wysokie - aby ogólnie działało z wieloma wątkami, wszystkie operacje iteratora musiałyby zablokować listę (aby upewnić się, że nie zmieni się w trakcie operacji), co spowoduje utworzenie listy operacje wolne. Pojawiłby się także nietrywialny narzut pamięci, a ponadto wymaga środowiska wykonawczego do obsługi miękkich odniesień, a IIRC pierwsza wersja CLR nie.
Z innej perspektywy, skopiowanie listy w celu jej modyfikacji pozwala użyć LINQ do określenia modyfikacji, co generalnie skutkuje wyraźniejszym kodem niż bezpośrednie iterowanie po liście, IMO.
źródło