Ich bin kürzlich auf diese häufig auftretende ungültige Operation Collection was modified
in C # gestoßen, und obwohl ich sie vollständig verstehe, scheint es sich um ein so häufig auftretendes Problem zu handeln (Google, ca. 300.000 Ergebnisse!). Es scheint aber auch logisch und unkompliziert zu sein, eine Liste zu ändern, während Sie sie durchgehen.
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"));
}
}
Einige Leute werden eine andere Liste erstellen, um sie zu durchlaufen, aber dies umgeht nur das Problem. Was ist die wirkliche Lösung oder was ist das eigentliche Designproblem hier? Wir alle scheinen dies zu wollen , aber deutet dies auf einen Konstruktionsfehler hin?
You consume an iterator from client code by using a For Each…Next (Visual Basic) or foreach (C#) statement
LinieIterator
(funktioniert wie beschrieben) undListIterator
(funktioniert wie gewünscht). Dies würde darauf hinweisen, dass die Bereitstellung von Iteratorfunktionen für eine Vielzahl von Auflistungstypen und -implementierungenIterator
äußerst restriktiv ist (was passiert, wenn Sie einen Knoten in einer ausgeglichenen Baumstruktur löschen? Ergibt keinennext()
Sinn mehr) undListIterator
aufgrund der geringeren Leistung leistungsfähiger ist Anwendungsfälle.Antworten:
Die kurze Antwort: nein
Einfach gesagt, erzeugen Sie undefiniertes Verhalten, wenn Sie eine Sammlung durchlaufen und gleichzeitig ändern. Denken Sie daran, das Element in einer Sequenz zu löschen
next
. Was würde passieren, wennMoveNext()
angerufen wird?Quelle: MSDN
Übrigens könntest du deine
RemoveAllBooks
zu einfach kürzenreturn new List<Book>()
Und um ein Buch zu entfernen, empfehle ich, eine gefilterte Sammlung zurückzugeben:
return books.Where(x => x.Author != "Bob").ToList();
Eine mögliche Regalimplementierung würde so aussehen:
quelle
Das Erstellen einer neuen Liste zum Ändern ist eine gute Lösung für das Problem, dass vorhandene Iteratoren der Liste nach der Änderung nur dann zuverlässig fortgesetzt werden können, wenn sie wissen, wie sich die Liste geändert hat.
Eine andere Lösung besteht darin, die Änderung mithilfe des Iterators und nicht über die Listenschnittstelle selbst vorzunehmen. Dies funktioniert nur, wenn es nur einen Iterator geben kann - es löst nicht das Problem, dass mehrere Threads gleichzeitig über die Liste iterieren, was beim Erstellen einer neuen Liste (solange auf veraltete Daten zugegriffen werden kann) der Fall ist.
Eine alternative Lösung, die die Framework-Implementierer hätten ergreifen können, besteht darin, die Liste alle ihre Iteratoren nachverfolgen zu lassen und sie zu benachrichtigen, wenn Änderungen auftreten. Der Aufwand für diesen Ansatz ist jedoch hoch. Damit er im Allgemeinen mit mehreren Threads funktioniert, müssen alle Iteratoroperationen die Liste sperren (um sicherzustellen, dass sie sich während der Ausführung der Operation nicht ändert), sodass alle Threads aufgelistet werden Operationen langsam. Es würde auch nicht-trivialen Speicher-Overhead geben, plus es erfordert die Laufzeit, um weiche Referenzen zu unterstützen, und IIRC, die erste Version der CLR, tat es nicht.
Wenn Sie die Liste aus einer anderen Perspektive kopieren, um sie zu ändern, können Sie LINQ verwenden, um die Änderung anzugeben. Dies führt im Allgemeinen zu einem klareren Code als das direkte Durchlaufen der Liste (IMO).
quelle