new method for c# list loops generics key-value

c# - method - ¿Cómo eliminar elementos de una lista genérica mientras se itera sobre ella?



new list object c# (22)

Como cualquier eliminación se toma en una condición que puede utilizar

list.RemoveAll(item => item.Value == someValue);

Estoy buscando un mejor patrón para trabajar con una lista de elementos que cada uno necesita procesar y luego, dependiendo del resultado, se eliminan de la lista.

No puede usar .Remove(element) dentro de un foreach (var element in X) (porque se Collection was modified; enumeration operation may not execute. Excepción) ... tampoco puede usar for (int i = 0; i < elements.Count(); i++) y .RemoveAt(i) porque interrumpe su posición actual en la colección con respecto a i .

¿Hay una manera elegante de hacer esto?


Copia la lista que estás iterando. Luego retire de la copia e interate el original. Ir hacia atrás es confuso y no funciona bien cuando se realiza un bucle en paralelo.

var ids = new List<int> { 1, 2, 3, 4 }; var iterableIds = ids.ToList(); Parallel.ForEach(iterableIds, id => { ids.Remove(id); });


El costo de eliminar un elemento de la lista es proporcional al número de elementos que siguen al que se eliminará. En el caso de que la primera mitad de los elementos sean elegibles para su eliminación, cualquier enfoque que se base en la eliminación de elementos individualmente tendrá que realizar operaciones de copia de elementos N * N / 4, lo que puede resultar muy costoso si la lista es grande .

Un enfoque más rápido es escanear la lista para encontrar el primer elemento que se eliminará (si lo hubiera) y, a partir de ese momento, copiar cada elemento que se debe conservar en el lugar al que pertenece. Una vez hecho esto, si se deben conservar los elementos R, los primeros elementos R en la lista serán esos elementos R, y todos los elementos que requieren eliminación estarán al final. Si esos elementos se eliminan en orden inverso, el sistema no tendrá que copiar ninguno de ellos, por lo que si la lista contenía N elementos de los cuales se conservaron los elementos R, incluida la primera F, será necesario copie los elementos de RF y reduzca la lista en un elemento NR veces. Todo el tiempo lineal.


El uso de .ToList () hará una copia de su lista, como se explica en esta pregunta: ToList (): ¿Crea una nueva lista?

Al usar ToList (), puedes eliminar de tu lista original, porque en realidad estás iterando sobre una copia.

foreach (var item in listTracked.ToList()) { if (DetermineIfRequiresRemoval(item)) { listTracked.Remove(item) } }


El uso de Remove o RemoveAt en una lista mientras se repite sobre esa lista se ha dificultado intencionalmente, porque casi siempre es lo incorrecto . Es posible que puedas hacer que funcione con algún truco inteligente, pero sería extremadamente lento. Cada vez que llama Remove tiene que explorar toda la lista para encontrar el elemento que desea eliminar. Cada vez que llame a RemoveAt tiene que mover los elementos posteriores 1 posición hacia la izquierda. Como tal, cualquier solución que use Remove o RemoveAt , requeriría un tiempo cuadrático, O (n²) .

Usa RemoveAll si puedes. De lo contrario, el siguiente patrón filtrará la lista en el tiempo lineal, O (n) .

// Create a list to be filtered IList<int> elements = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); // Filter the list int kept = 0; for (int i = 0; i < elements.Count; i++) { // Test whether this is an element that we want to keep. if (elements[i] % 3 > 0) { // Add it to the list of kept elements. elements[kept] = elements[i]; kept++; } } // Unfortunately IList has no Resize method. So instead we // remove the last element of the list until: elements.Count == kept. while (kept < elements.Count) elements.RemoveAt(elements.Count-1);


El uso de ToArray () en una lista genérica le permite hacer un Eliminar (elemento) en su Lista genérica:

List<String> strings = new List<string>() { "a", "b", "c", "d" }; foreach (string s in strings.ToArray()) { if (s == "b") strings.Remove(s); }


Iterar su lista al revés con un bucle for:

for (int i = safePendingList.Count - 1; i >= 0; i--) { // some code // safePendingList.RemoveAt(i); }

Ejemplo:

var list = new List<int>(Enumerable.Range(1, 10)); for (int i = list.Count - 1; i >= 0; i--) { if (list[i] > 5) list.RemoveAt(i); } list.ForEach(i => Console.WriteLine(i));

Alternativamente, puede usar el método RemoveAll con un predicado para probar contra:

safePendingList.RemoveAll(item => item.Value == someValue);

Aquí hay un ejemplo simplificado para demostrar:

var list = new List<int>(Enumerable.Range(1, 10)); Console.WriteLine("Before:"); list.ForEach(i => Console.WriteLine(i)); list.RemoveAll(i => i > 5); Console.WriteLine("After:"); list.ForEach(i => Console.WriteLine(i));


La iteración inversa debe ser lo primero que se te ocurra cuando desees eliminar elementos de una Colección al iterar sobre ella.

Afortunadamente, hay una solución más elegante que escribir un bucle for que implica escribir innecesariamente y puede ser propenso a errores.

ICollection<int> test = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); foreach (int myInt in test.Reverse<int>()) { if (myInt % 2 == 0) { test.Remove(myInt); } }


La mejor manera de eliminar elementos de una lista mientras se realiza una iteración sobre ella es utilizar RemoveAll() . Pero la principal preocupación escrita por las personas es que tienen que hacer algunas cosas complejas dentro del bucle y / o tener complejos casos de comparación.

La solución es seguir usando RemoveAll() pero usar esta notación:

var list = new List<int>(Enumerable.Range(1, 10)); list.RemoveAll(item => { // Do some complex operations here // Or even some operations on the items SomeFunction(item); // In the end return true if the item is to be removed. False otherwise return item > 5; });


Me encontré en una situación similar en la que tuve que eliminar todos los elementos en una List<T> determinada List<T> .

for (int i = 0, j = 0, n = 3; i < list.Count; i++) { if ((j + 1) % n == 0) //Check current iteration is at the nth interval { list.RemoveAt(i); j++; //This extra addition is necessary. Without it j will wrap //down to zero, which will throw off our index. } j++; //This will always advance the j counter }


Mi enfoque es que primero creo una lista de índices, que deberían eliminarse. Luego, recorro los índices y elimino los elementos de la lista inicial. Esto se ve así:

var messageList = ...; // Restrict your list to certain criteria var customMessageList = messageList.FindAll(m => m.UserId == someId); if (customMessageList != null && customMessageList.Count > 0) { // Create list with positions in origin list List<int> positionList = new List<int>(); foreach (var message in customMessageList) { var position = messageList.FindIndex(m => m.MessageId == message.MessageId); if (position != -1) positionList.Add(position); } // To be able to remove the items in the origin list, we do it backwards // so that the order of indices stays the same positionList = positionList.OrderByDescending(p => p).ToList(); foreach (var position in positionList) { messageList.RemoveAt(position); } }


No puede usar foreach, pero podría iterar hacia adelante y administrar su variable de índice de bucle cuando elimine un elemento, de esta forma:

for (int i = 0; i < elements.Count; i++) { if (<condition>) { // Decrement the loop counter to iterate this index again, since later elements will get moved down during the remove operation. elements.RemoveAt(i--); } }

Tenga en cuenta que, en general, todas estas técnicas se basan en el comportamiento de la colección que se está iterando. La técnica que se muestra aquí funcionará con la Lista estándar (T). (Es bastante posible escribir su propia clase de colección e iterador que permita la eliminación de elementos durante un bucle foreach).


Seleccione los elementos que desea en lugar de intentar eliminar los elementos que no desea. Esto es mucho más fácil (y generalmente más eficiente también) que eliminar elementos.

var newSequence = (from el in list where el.Something || el.AnotherThing < 0 select el);

Quería publicar esto como un comentario en respuesta al comentario dejado por Michael Dillon a continuación, pero es demasiado largo y probablemente útil en mi respuesta de todos modos:

Personalmente, nunca eliminaría los elementos uno por uno, si necesita eliminarse, luego llame a RemoveAll que toma un predicado y solo reorganiza la matriz interna una vez, mientras que Remove realiza una operación Array.Copy para cada elemento que elimine. RemoveAll es mucho más eficiente.

Y cuando está iterando hacia atrás sobre una lista, ya tiene el índice del elemento que desea eliminar, por lo que sería mucho más eficiente llamar a RemoveAt , porque Remove primero hace un recorrido de la lista para encontrar el índice de Elemento que intentas eliminar, pero ya conoces ese índice.

Así que, en general, no veo ninguna razón para llamar a Remove en un bucle for. E idealmente, si es posible, use el código anterior para transmitir elementos de la lista según sea necesario, de modo que no se deba crear una segunda estructura de datos.


Si la función que determina qué elementos eliminar no tiene efectos secundarios y no muta el elemento (es una función pura), una solución simple y eficiente (tiempo lineal) es:

list.RemoveAll(condition);

Si hay efectos secundarios, usaría algo como:

var toRemove = new HashSet<T>(); foreach(var item in items) { ... if(condition) toRemove.Add(item); } items.RemoveAll(toRemove.Contains);

Este es todavía un tiempo lineal, asumiendo que el hash es bueno. Pero tiene un mayor uso de memoria debido al hashset.

Finalmente, si su lista es solo una IList<T> lugar de una List<T> , sugiero mi respuesta a ¿Cómo puedo hacer este iterador especial para cada iterador? . Esto tendrá un tiempo de ejecución lineal dado las implementaciones típicas de IList<T> , en comparación con el tiempo de ejecución cuadrático de muchas otras respuestas.


Suponiendo que el predicado es una propiedad booleana de un elemento, que si es verdadero, entonces el elemento debe eliminarse:

int i = 0; while (i < list.Count()) { if (list[i].predicate == true) { list.RemoveAt(i); continue; } i++; }


Una solución simple y directa:

Use un bucle for estándar que se ejecuta hacia atrás en su colección y RemoveAt(i) para eliminar elementos.


Yo reasignaría la lista de una consulta LINQ que filtraba los elementos que no quería mantener.

list = list.Where(item => ...).ToList();

A menos que la lista sea muy grande, no debería haber problemas significativos de rendimiento al hacer esto.


Ojalá el "patrón" fuera algo como esto:

foreach( thing in thingpile ) { if( /* condition#1 */ ) { foreach.markfordeleting( thing ); } elseif( /* condition#2 */ ) { foreach.markforkeeping( thing ); } } foreachcompleted { // then the programmer''s choices would be: // delete everything that was marked for deleting foreach.deletenow(thingpile); // ...or... keep only things that were marked for keeping foreach.keepnow(thingpile); // ...or even... make a new list of the unmarked items others = foreach.unmarked(thingpile); }

Esto alinearía el código con el proceso que ocurre en el cerebro del programador.


foreach (var item in list.ToList()) { list.Remove(item); }

Si agrega ".ToList ()" a su lista (o los resultados de una consulta LINQ), puede eliminar "item" directamente de "list" sin que se haya modificado la temida " Colección; la operación de enumeración puede no ejecutarse ". error. El compilador hace una copia de "lista", para que pueda hacer la eliminación en la matriz de forma segura.

Si bien este patrón no es súper eficiente, tiene una sensación natural y es lo suficientemente flexible para casi cualquier situación . Por ejemplo, cuando desea guardar cada "elemento" en una base de datos y eliminarlo de la lista solo cuando el guardado de la base de datos tenga éxito.


List<T> TheList = new List<T>(); TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element));


foreach(var item in list.ToList()) { if(item.Delete) list.Remove(item); }

Simplemente crea una lista completamente nueva a partir de la primera. Digo "Fácil" en lugar de "Correcto", ya que crear una lista completamente nueva probablemente tenga una prima de rendimiento sobre el método anterior (no me he molestado en ninguna evaluación comparativa). Generalmente prefiero este patrón, también puede ser útil para superar Limitaciones de Linq-To-Entities.

for(i = list.Count()-1;i>=0;i--) { item=list[i]; if (item.Delete) list.Remove(item); }

De esta manera, recorre la lista hacia atrás con un antiguo bucle For. Hacer esto hacia adelante podría ser problemático si el tamaño de la colección cambia, pero al revés siempre debería ser seguro.


myList.RemoveAt(i--); simples;