J'ai récemment rencontré cette opération invalide courante Collection was modified
en C #, et même si je la comprends parfaitement, cela semble être un problème si courant (google, environ 300 000 résultats!). Mais il semble également être une chose logique et simple de modifier une liste pendant que vous la parcourez.
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"));
}
}
Certaines personnes créeront une autre liste à parcourir, mais cela évite simplement le problème. Quelle est la vraie solution, ou quel est le véritable problème de conception ici? Nous semblons tous vouloir le faire, mais cela indique-t-il un défaut de conception?
You consume an iterator from client code by using a For Each…Next (Visual Basic) or foreach (C#) statement
ligneIterator
(fonctionne comme vous l'avez décrit) etListIterator
(fonctionne comme vous le souhaitez). Cela indiquerait que pour fournir des fonctionnalités d'itérateur sur un large éventail de types de collections et d'implémentations, ilIterator
est extrêmement restrictif (que se passe-t-il si vous supprimez un nœud dans une arborescence équilibrée? Cela anext()
plus de sens), avecListIterator
plus de puissance car moins cas d'utilisation.Réponses:
La réponse courte: non
En termes simples, vous produisez un comportement indéfini lorsque vous parcourez une collection et la modifiez en même temps. Pensez à supprimer l'
next
élément dans une séquence. Que se passerait-il siMoveNext()
on l'appelle?Source: MSDN
Soit dit en passant, vous pouvez réduire votre
RemoveAllBooks
simplementreturn new List<Book>()
Et pour retirer un livre, je recommande de retourner une collection filtrée:
return books.Where(x => x.Author != "Bob").ToList();
Une mise en œuvre possible en étagère ressemblerait à:
la source
La création d'une nouvelle liste pour la modifier est une bonne solution au problème selon lequel les itérateurs existants de la liste ne peuvent pas continuer de manière fiable après la modification à moins qu'ils ne sachent comment la liste a changé.
Une autre solution consiste à effectuer la modification à l'aide de l'itérateur plutôt que via l'interface de liste elle-même. Cela ne fonctionne que s'il ne peut y avoir qu'un seul itérateur - cela ne résout pas le problème pour plusieurs threads itérant simultanément sur la liste, ce qui (tant que l'accès aux données périmées est acceptable) crée une nouvelle liste.
Une solution alternative que les implémenteurs de l'infrastructure auraient pu prendre consiste à faire en sorte que la liste suive tous ses itérateurs et les avertisse lorsque des changements se produisent. Cependant, les frais généraux de cette approche sont élevés - pour qu'elle fonctionne en général avec plusieurs threads, toutes les opérations de l'itérateur devraient verrouiller la liste (pour s'assurer qu'elle ne change pas pendant que l'opération est en cours), ce qui rendrait toute la liste les opérations sont lentes. Il y aurait également une surcharge de mémoire non triviale, de plus, il nécessite que le runtime prenne en charge les références logicielles, contrairement à la première version du CLR de l'IICRC.
Dans une perspective différente, la copie de la liste pour la modifier vous permet d'utiliser LINQ pour spécifier la modification, ce qui se traduit généralement par un code plus clair que l'itération directe sur la liste, IMO.
la source