Recientemente me encontré con esta operación inválida común Collection was modified
en C #, y aunque lo entiendo completamente, parece ser un problema tan común (¡Google, alrededor de 300 mil resultados!). Pero también parece ser lógico y sencillo modificar una lista mientras la revisas.
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"));
}
}
Algunas personas crearán otra lista para iterar, pero esto solo está esquivando el problema. ¿Cuál es la solución real o cuál es el problema de diseño real aquí? Todos parecemos querer hacer esto, pero ¿es indicativo de una falla de diseño?
You consume an iterator from client code by using a For Each…Next (Visual Basic) or foreach (C#) statement
líneaIterator
(funciona como lo describiste) yListIterator
(funciona como deseas). Esto indicaría que, para proporcionar funcionalidad de iterador en una amplia gama de tipos de colecciones e implementaciones,Iterator
es extremadamente restrictivo (¿qué sucede si elimina un nodo en un árbol equilibrado?next()
Ya tiene sentido),ListIterator
siendo más poderoso debido a que tiene menos casos de usoRespuestas:
La respuesta corta: no
En pocas palabras, produce un comportamiento indefinido cuando itera a través de una colección y la modifica al mismo tiempo. Piense en eliminar el
next
elemento en una secuencia. Que pasaria siMoveNext()
se llama?Fuente: MSDN
Por cierto, podrías acortar tu
RemoveAllBooks
simplementereturn new List<Book>()
Y para eliminar un libro, recomiendo devolver una colección filtrada:
return books.Where(x => x.Author != "Bob").ToList();
Una posible implementación de estantería se vería así:
fuente
Crear una nueva lista para modificarla es una buena solución al problema de que los iteradores existentes de la lista no pueden continuar de manera confiable después del cambio a menos que sepan cómo ha cambiado la lista.
Otra solución es realizar la modificación utilizando el iterador en lugar de hacerlo a través de la interfaz de la lista. Esto solo funciona si solo puede haber un iterador; no resuelve el problema de varios hilos que iteran sobre la lista simultáneamente, lo que (siempre y cuando el acceso a datos obsoletos sea aceptable) crea una nueva lista.
Una solución alternativa que los implementadores del marco podrían haber tomado es hacer que la lista rastree todos sus iteradores y les notifique cuando ocurran cambios. Sin embargo, los gastos generales de este enfoque son altos: para que funcione en general con varios subprocesos, todas las operaciones de iterador necesitarían bloquear la lista (para asegurarse de que no cambie mientras la operación está en progreso), lo que haría que toda la lista operaciones lentas. También habría una sobrecarga de memoria no trivial, además de que requiere el tiempo de ejecución para admitir referencias suaves, y IIRC la primera versión del CLR no.
Desde una perspectiva diferente, copiar la lista para modificarla le permite usar LINQ para especificar la modificación, lo que generalmente da como resultado un código más claro que iterar directamente sobre la lista, IMO.
fuente