¿Por qué C# reutiliza la variable en foreach? Explicación para principiantes

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: el enigma que atormenta a los principiantes

\n\n

Imagínate esta situación: escribes un bucle foreach simple en C#, guardas una expresión lambda o ejecutas una tarea asíncrona dentro del bucle, y de repente obtienes un resultado completamente inesperado. Todas las variables que, aparentemente, "capturaste" cuidadosamente, de repente hacen referencia al último elemento de la colección. ¿Te suena familiar?

\n\n

En Stack Overflow, la pregunta "Is there a reason for C#'s reuse of the variable in a foreach?" ha acumulado miles de visitas y numerosos comentarios. No es solo un interés académico: es una trampa práctica en la que incluso los desarrolladores experimentados caen. En este artículo, entenderemos por qué C# hace esto, cómo ha cambiado con las versiones del lenguaje y, lo más importante, cómo evitar los errores relacionados.

\n\n

Hablaremos en un lenguaje sencillo, con ejemplos de código, para que incluso un principiante en C# entienda la esencia del problema. ¡Allá vamos!

\n\n

El núcleo del problema: ¿qué significa "reutilización de variable"?

\n\n

En el bucle foreach clásico de C# (antes de la versión 5.0), la variable de iteración se declaraba una sola vez para todo el bucle, no se creaba de nuevo en cada iteración. Esto significa que si capturas esta variable en un closure (por ejemplo, en una expresión lambda o un método anónimo), todos los closures harán referencia a la misma variable.

\n\n

Veamos el ejemplo clásico del problema:

\n\n
var actions = new List<Action>();\nvar numbers = new List<int> { 1, 2, 3, 4, 5 };\n\nforeach (var number in numbers)\n{\n    actions.Add(() => Console.WriteLine(number));\n}\n\n// Ejecutamos todas las acciones\nforeach (var action in actions)\n{\n    action();\n}\n
\n\n

Pregunta: ¿qué imprime este código? Intuitivamente, parece que 1, 2, 3, 4, 5. Pero en realidad (antes de C# 5) imprimiría 5, 5, 5, 5, 5.

\n\n

¿Por qué? Porque la variable number existe en una única instancia durante todo el bucle. Cuando el bucle termina, su valor es igual al último elemento (5). Todas las lambdas capturaron una referencia a esta variable, no su valor en el momento de la creación.

\n\n

Analogía para entenderlo

\n\n

Imagina que tienes una caja (variable) y colocas en ella diferentes objetos (valores) uno tras otro. Les das a tus amigos (lambdas) no los objetos en sí, sino la llave de la caja. Cuando tus amigos vienen a recoger sus objetos, abren la caja y allí está el último objeto que colocaste. Así funciona el foreach antiguo.

\n\n

Contexto histórico: ¿por qué se hizo así?

\n\n

Muchos principiantes preguntan: "¡Esto es un bug! ¿Por qué hacerlo así?" En realidad, fue una decisión consciente de los desarrolladores de C#, basada en el rendimiento y la simplicidad de la compilación.

\n\n
    \n
  • Rendimiento: Crear una nueva variable en cada iteración es una asignación de memoria adicional. Para la mayoría de los bucles simples, esto no es crítico, pero en sistemas de alta carga, cada microoptimización es importante.
  • \n
  • Compatibilidad con for: En el bucle for, la variable también se declara una sola vez. Los desarrolladores querían mantener la uniformidad.
  • \n
  • Simplicidad del compilador: El enfoque antiguo era más fácil de implementar en el compilador. Pero el precio fue un comportamiento no intuitivo para los programadores.
  • \n
\n\n

Sin embargo, la comunidad rápidamente descubrió esta "característica" y comenzó a reportar errores. En Stack Overflow, la pregunta "Is there a reason for C#'s reuse of the variable in a foreach?" se convirtió en una de las más populares. Microsoft escuchó y en C# 5.0 (2012) se cambió el comportamiento.

\n\n

¿Qué cambió en C# 5.0?

\n\n

A partir de C# 5.0, el compilador crea una nueva variable para cada iteración del bucle foreach. Ahora, el ejemplo anterior funcionará de manera intuitivamente correcta e imprimirá 1, 2, 3, 4, 5.

\n\n
// Esto                

Blogs

Book Recommendations