2010-08-23 12 views
14

Tengo un ciclo que recorre los elementos de una lista. Estoy obligado a eliminar elementos de esta lista dentro del ciclo en función de ciertas condiciones. Cuando trato de hacer esto en C#, obtengo una excepción. aparentemente, no está permitido eliminar elementos de la lista que se está iterando. El problema se observó con un ciclo foreach. ¿Hay alguna manera estándar de evitar este problema?Problemas al eliminar elementos de una lista al iterar a través de la lista

Nota: Una solución que podría pensar es crear una copia de la lista únicamente para fines de iteración y para eliminar elementos de la lista original dentro del ciclo. Estoy buscando una mejor manera de lidiar con esto.

+4

¿Por qué wiki de la comunidad? – LukeH

Respuesta

14

Al utilizar List<T> el método ToArray() ayuda en este escenario enormemente:

List<MyClass> items = new List<MyClass>(); 
foreach (MyClass item in items.ToArray()) 
{ 
    if (/* condition */) items.Remove(item); 
} 

La alternativa es usar un bucle for en lugar de un foreach, pero luego debe disminuir la variable de índice siempre que elimine un elemento, es decir,

List<MyClass> items = new List<MyClass>(); 
for (int i = 0; i < items.Count; i++) 
{ 
    if (/* condition */) 
    { 
     items.RemoveAt(i); 
     i--; 
    } 
} 
+0

Si está utilizando una 'Lista ', ¿por qué no utilizar el método incorporado 'RemoveAll' para este escenario? – LukeH

+0

Me niego a utilizar métodos que tengan como parámetro a los delegados, porque o bien tiene que crear un método nuevo o agregar un método anónimo al bloque actual. Si hace esto, su código se vuelve incompatible con edit-and-continue cuando se depura. –

+3

¿Por qué la operación de copia innecesaria en ToArray()? –

1

La solución recomendada es poner todos los elementos que desea eliminar en una lista separada y después del primer ciclo, ponga un segundo ciclo donde itere sobre la lista de eliminación y elimine esos elementos de la primera lista.

+3

¿Recomendado por quién? Este es un enfoque inútilmente costoso, sin ningún beneficio. –

4

Se podría utilizar LINQ para reemplazar la lista inicial de una nueva lista al filtrar los artículos:

IEnumerable<Foo> initialList = FetchList(); 
initialList = initialList.Where(x => SomeFilteringConditionOnElement(x)); 
// Now initialList will be filtered according to the condition 
// The filtered elements will be subject to garbage collection 

De esta manera usted no tiene que preocuparse acerca de los bucles.

+4

O, si la lista es una 'Lista ', entonces podría usar el método 'RemoveAll'. – LukeH

+0

@LukeH, buen punto. –

5

Puede utilizar la indexación número entero para eliminar elementos:

List<int> xs = new List<int> { 1, 2, 3, 4 }; 
for (int i = 0; i < xs.Count; ++i) 
{ 
    // Remove even numbers. 
    if (xs[i] % 2 == 0) 
    { 
     xs.RemoveAt(i); 
     --i; 
    } 
} 

Esto puede ser extraño para leer y difícil de mantener, sin embargo, especialmente si la lógica en el bucle se pone más compleja.

13

Si su lista es un real List<T> continuación, puede utilizar el incorporado en RemoveAll método para eliminar elementos basados ​​en un predicado:

int numberOfItemsRemoved = yourList.RemoveAll(x => ShouldThisItemBeDeleted(x)); 
+0

+1 definitivamente el método más fácil! – Abel

+0

este método supone que tiene un tipo de filtro. no es útil cuando no lo tienes, cuál es la pregunta. –

+0

@Lord Black: la pregunta dice específicamente que los elementos deben eliminarse "en función de ciertas condiciones" _; mi función hipotética 'ShouldThisItemBeDeleted' sería una expresión de esas condiciones. – LukeH

0

El motivo por el que aparece un error es porque está utilizando un bucle foreach. Si piensas cómo funciona un bucle Foreach esto tiene sentido. El bucle foreach llama al método GetEnumerator en la lista. Si va a cambiar el número de elementos en la Lista, el Enumerador que tiene el bucle foreach no tendrá la cantidad correcta de elementos. Si elimina un elemento, se lanzará un error de excepción nulo, y si agrega un elemento, el ciclo se perderá un elemento.

Si te gustan las expresiones Linq y Lamda, recomendaría la solución Darin Dimitrov, de lo contrario utilizaría la solución provista por Chris Schmich.

2

Otro truco es recorrer la lista al revés ... eliminar un elemento no afectará a ninguno de los elementos que encontrará en el resto del ciclo.

No estoy recomendando esto ni nada más. Todo lo que necesita para esto probablemente se puede hacer usando declaraciones LINQ para filtrar la lista según sus requisitos.

0

Se puede recorrer con foreach esta manera:

List<Customer> custList = Customer.Populate(); 

foreach (var cust in custList.ToList()) 

{ 

     custList.Remove(cust); 

} 

Nota: ToList en la lista de variables, esta itera a través de la lista creada por el ToList pero elimina los elementos de la lista original.

Espero que esto ayude.

Cuestiones relacionadas