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


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

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

Hay una manera elegante de hacer esto?

Author: Vikrant, 2009-10-17

22 answers

Itere su lista en sentido inverso 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 con:

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));
 585
Author: Ahmad Mageed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-17 14:46:02

Una solución simple y directa:

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

 75
Author: Jan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-07 06:35:07

La iteración inversa debería ser lo primero que se te ocurra cuando quieras eliminar elementos de una colección mientras se itera 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);
    }
}
 58
Author: jedesah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-10 19:39:21
 foreach (var item in list.ToList()) {
     list.Remove(item);
 }

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

Mientras que este patrón no es súper eficiente, tiene una sensación natural y es lo suficientemente flexible para casi cualquier situación. Como cuando quieres guarde cada "elemento" en una base de datos y elimínelo de la lista solo cuando la base de datos se guarde correctamente.

 39
Author: Greg Little,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-08 23:34:56

Usar toArray() en una lista genérica te permite hacer un Remove (item) en tu 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);
        }
 20
Author: Etienne Brouillard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-17 14:46:09

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 para tener en mi respuesta de todos modos:

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

Y cuando estás iterando hacia atrás sobre una lista, ya tienes el índice del elemento que quieres 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 del elemento que estás tratando de eliminar, pero ya conoces ese índice.

Así que en general, yo 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 para que no se tenga que crear una segunda estructura de datos.

 20
Author: JulianR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-17 19:47:59

Usando .ToList() hará una copia de tu lista, como se explica en esta pregunta: ToList () Create ¿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)
        }

     }
 17
Author: StuartQ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:24

Como cualquier remove se toma con una condición, puede usar

list.RemoveAll(item => item.Value == someValue);
 10
Author: Ahmad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-22 10:58:23

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);

Esto sigue siendo tiempo lineal, suponiendo que el hash es bueno. Pero tiene un mayor uso de memoria debido al hashset.

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

 10
Author: CodesInChaos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:47
List<T> TheList = new List<T>();

TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element));
 9
Author: Mauricio Ramalho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-24 08:44:21

No puedes usar foreach, pero puedes iterar hacia adelante y administrar tu variable de índice de bucle cuando eliminas un elemento, de la siguiente manera:

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 itera. La técnica mostrada aquí funcionará con la Lista estándar (T). (Es muy posible escribir su propia clase de colección e iterador que hace permitir la eliminación de elementos durante un bucle foreach.)

 5
Author: yoyo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-18 21:37:36

Usar Remove o RemoveAten una lista mientras se itera sobre esa lista se ha hecho intencionalmente difícil, porque casi siempre es lo incorrecto. Es posible que pueda hacerlo funcionar con algún truco inteligente, pero sería extremadamente lento. Cada vez que llame a Remove tiene que escanear toda la lista para encontrar el elemento que desea eliminar. Cada vez que llame a RemoveAt tiene que mover los elementos siguientes 1 posición a la izquierda. Como tal, cualquier solución que utilice Remove o RemoveAt, requeriría tiempo cuadrático, O(n2).

Usa RemoveAll si puedes. De lo contrario, el siguiente patrón filtrará la lista in-place en 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);
 4
Author: bcmpinc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-09 21:12:43

I deseo el" patrón " era algo así:

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.

 3
Author: warrens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-07 14:10:03

Asumiendo que predicado es una propiedad booleana de un elemento, que si es true, entonces el elemento debe ser eliminado:

        int i = 0;
        while (i < list.Count())
        {
            if (list[i].predicate == true)
            {
                list.RemoveAt(i);
                continue;
            }
            i++;
        }
 2
Author: roeesha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-30 09:28:05

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 de rendimiento significativos al hacer esto.

 2
Author: Martin Liversage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-10 14:13:55

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

La solución es seguir utilizando RemoveAll() pero usa 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;
});
 2
Author: Hüseyin Yağlı,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-29 15:32:29
foreach(var item in list.ToList())

{

if(item.Delete) list.Remove(item);

}

Simplemente cree una lista completamente nueva a partir de la primera. Digo " Fácil "en lugar de" Correcto", ya que la creación de una lista completamente nueva probablemente tenga una prima de rendimiento sobre el método anterior (no me he molestado con ningún benchmarking.) Generalmente prefiero este patrón, también puede ser útil para superar las 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, los ciclos a través de la lista hacia atrás con un simple bucle For antiguo. Hacer esto hacia adelante podría ser problemático si el tamaño de la la colección cambia, pero al revés siempre debe ser seguro.

 1
Author: Lijo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-19 12:40:19

Me encontré en una situación similar donde tuve que eliminar cada n th elemento en un List<T> dado.

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
}
 0
Author: Paul Nelson Baker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-19 05:24:54

El costo de eliminar un artículo de la lista es proporcional al número de artículos que siguen al que se va a eliminar. En el caso de que la primera mitad de los elementos califiquen para la eliminación, cualquier enfoque que se base en la eliminación de elementos individualmente terminará teniendo que realizar sobre N * N / 4 operaciones de copia de elementos, que pueden ser muy costosas si la lista es grande.

Un enfoque más rápido es explorar la lista para encontrar el primer elemento que se eliminará (si lo hay), y luego desde ese punto copia hacia adelante cada elemento que debe ser retenido en el lugar donde pertenece. Una vez hecho esto, si R elementos deben ser retenidos, los primeros elementos R en la lista serán los 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 tenía N elementos de los cuales R elementos, incluidos todos los primeros F, se conservaron, será necesario copiar los elementos R-F, y reducir la lista en uno item N-R times. Todo tiempo lineal.

 0
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-18 17:29:01

Mi enfoque es que primero creo una lista de índices, que debería eliminarse. Después me bucle sobre los índices y retire 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);
    }
}
 0
Author: testing,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-18 14:40:54

Copie la lista que está iterando. A continuación, retire de la copia e intercale el original. Ir hacia atrás es confuso y no funciona bien cuando se hace un bucle en paralelo.

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

Parallel.ForEach(iterableIds, id =>
{
    ids.Remove(id);
});
 0
Author: Timothy Gonzalez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-24 09:00:57
myList.RemoveAt(i--);

simples;
 -4
Author: S.W.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-06 14:10:19