CodeGym /Cursos /C# SELF /Eliminar un elemento de una colección en un bucle

Eliminar un elemento de una colección en un bucle foreach

C# SELF
Nivel 29 , Lección 4
Disponible

1. Introducción

Casi todo el mundo que empieza a programar en C# tarde o temprano se topa con el mismo problema: tienes una colección (por ejemplo, una lista de objetos) y necesitas eliminar de ella los elementos que no te sirven según alguna condición. Suena fácil, y lo primero que piensas es usar el bucle foreach, porque es la forma más “segura” y “amigable” de recorrer cosas. Pero de repente, en el momento menos esperado, aparece un misterioso error en tiempo de ejecución que no estaba en los ejemplos sencillos, y tu programa se para en seco.

Vamos a ver por qué pasa esto, qué ocurre “por debajo del capó” con las colecciones e iteradores, y cómo eliminar elementos de forma inteligente para evitar sorpresas y bugs.

¿Por qué foreach no se lleva bien con eliminar elementos?

Para entenderlo, imagina una cola de personas (esa es nuestra colección). Vas por la cola preguntando a cada uno: "¿Te dejo o te borro?" Si empiezas a borrar a alguien justo mientras recorres la cola, toda la fila se mueve, la gente cambia de sitio, y tu plan de “la siguiente persona es la siguiente en la lista” se va al garete. Puede que alguien no sea preguntado o que preguntes a alguien dos veces.

Ejemplo en C#:


List<string> names = new List<string> { "Antón", "Boris", "Vika", "Grisha" };

foreach (string name in names)
{
    if (name.StartsWith("V"))
        names.Remove(name); // ¡Boom! InvalidOperationException
}

Cuando el programa llegue a "Vika" y decida eliminarla, el iterador interno pierde el “contacto con la realidad” — y verás este mensaje:
InvalidOperationException: Collection was modified; enumeration operation may not execute.

No es un capricho — así C# te protege de bugs difíciles de pillar y de romper la estructura de datos.

2. ¿Por qué un código tan simple no funciona?

¿Cómo funciona todo por dentro?

Cuando escribes un bucle foreach, el compilador genera un objeto especial — un iterador (IEnumerator), que recuerda la posición actual en la colección. Este objeto sabe cuántos elementos había al principio, cuál es el elemento “activo” ahora, y controla estrictamente que la colección no cambie mientras la recorres.

Cualquier intento de eliminar o añadir un elemento dentro de un foreach rompe ese contrato. ¿Por qué? Si después de eliminar un elemento los índices cambian, el iterador ya no puede pasar correctamente al siguiente elemento. Puede que alguien se quede sin revisar, o que revises a alguien dos veces — al final, un lío total. Por eso, en cuanto cambias la colección, .NET lanza el error sin dudarlo.

¿Qué pasa si eliminas “a lo bruto”?

Imagina que escribes este programa:


List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
foreach (int x in numbers)
{
    if (x % 2 == 0)
        numbers.Remove(x);
}

Parece lógico: recorres todos los números y eliminas los pares. Pero en la segunda vuelta el programa lanza el error — “la colección fue modificada durante la enumeración”.

A veces te entran ganas de saltarte la advertencia y probar “bajo tu propio riesgo”. Pero aunque no hubiera error, según la estructura de la colección el resultado sería impredecible. Por ejemplo, podrías saltarte elementos o no eliminar todos los que querías.

3. ¿Entonces cómo se hace bien?

Técnica nº1: Bucle for al revés

El tema es que al eliminar, los elementos siguientes se “mueven” a la izquierda, y si vas desde el principio de la lista, puedes liarte con los índices y saltarte elementos. Para evitarlo, lo mejor es ir desde el final.


List<string> names = new List<string> { "Antón", "Boris", "Vika", "Grisha" };

for (int i = names.Count - 1; i >= 0; i--)
{
    if (names[i].StartsWith("V"))
        names.RemoveAt(i);
}

En este ejemplo, después de cada eliminación todos los elementos que vienen después se mueven, pero los índices que aún no has procesado no se ven afectados. Así no te saltas nada.

Técnica nº2: Filtrar y crear una lista nueva

A veces es más fácil (y muchas veces más rápido) recorrer la colección, quedarte solo con los elementos que quieres mantener y reemplazar la lista original por la nueva.


var names = new List<string> { "Antón", "Boris", "Vika", "Grisha" };
names = names.Where(name => !name.StartsWith("V")).ToList();
// Al final quedan "Antón" y "Grisha"

Este método va bien cuando la colección no es muy grande o no es crítico mantener la referencia original.

Técnica nº3: Usar métodos especiales de las colecciones

Si trabajas con un List<T> clásico, para eliminar según una condición tienes un método muy cómodo:


names.RemoveAll(name => name.StartsWith("V"));

Todo el proceso se hace bien por dentro, y tu código queda limpio y claro.

Técnica nº4: Marcar para eliminar

Hay colecciones que no puedes modificar “al vuelo” (por ejemplo, Dictionary, HashSet, o incluso tu propia clase). En esos casos se usa el truco de “marcar para eliminar”:

  1. Primero recorres la colección y apuntas todos los elementos que quieres borrar en una lista aparte.
  2. Luego recorres esa lista nueva y eliminas los elementos de la colección original.

Dictionary<int, string> dict = new Dictionary<int, string> { [1] = "uno", [2] = "dos", [3] = "tres" };
var toDelete = new List<int>();
foreach (var kvp in dict)
{
    if (kvp.Key % 2 == 0)
        toDelete.Add(kvp.Key);
}
foreach (var key in toDelete)
    dict.Remove(key);

4. Detalles útiles

Errores y mitos de principiantes

Uno de los errores más comunes es esperar que eliminar un elemento de una colección mientras la recorres funcione “de alguna manera”, porque en otros lenguajes (como Python) a veces sí se puede. Pero en C# esto está totalmente prohibido por tu seguridad: es mucho mejor recibir un error claro que un bug silencioso y traicionero que nadie podrá reproducir después.

Otro error típico es usar un bucle for aumentando el índice en vez de disminuirlo. Así, después de eliminar un elemento, todos los siguientes se “mueven” y te saltas algunos. Siempre ve del final al principio si eliminas por índice.

La moraleja

La tarea de “eliminar elementos de una colección según una condición” aparece en la mitad de los programas en C#, pero hacerlo directamente dentro de un bucle foreach está prohibido — así lo dicta la arquitectura del lenguaje, para cuidar la integridad de tus datos y evitar errores raros.
Recuerda esta regla y te ahorrarás noches en vela con el debugger.

Cómo hacerlo siempre bien

  • Nunca elimines elementos de una colección directamente en un bucle foreach. Eso da error en tiempo de ejecución.
  • Para listas (List<T>) y arrays, usa un bucle for desde el final, o los métodos RemoveAll y filtrado con LINQ.
  • Para diccionarios, conjuntos y otras colecciones complejas — primero apunta los elementos a eliminar, luego recorre esa lista y bórralos de la colección original.
  • Si tienes dudas — piensa: ¿cómo cambia la colección al eliminar? ¿Qué le pasa al iterador? Si tienes la más mínima duda, es que ese método no es el correcto.
1
Cuestionario/control
Filtrado de elementos, nivel 29, lección 4
No disponible
Filtrado de elementos
Trabajo con colecciones
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION