¿Por qué necesitas yield en C#? El secreto de las colecciones perezosas
Imagina: estás escribiendo un método que debe devolver una lista de números del 1 al millón. Lo primero que viene a la mente es crear un List<int>, llenarlo y devolverlo. Pero, ¿qué pasa si el código que llama solo quiere los primeros 10 elementos? De todas formas, perdiste tiempo creando un millón de objetos, ocupaste memoria y todo fue en vano.
Aquí entra en escena la palabra clave yield en C#. No es solo azúcar sintáctico, sino una herramienta poderosa para crear secuencias perezosas (lazy). En este artículo analizaremos yield return y yield break con ejemplos reales, entenderemos cómo funcionan internamente y aprenderemos a aplicarlos en el desarrollo diario.
Este tutorial está basado en una pregunta real de Stack Overflow que acumuló miles de visitas. Lo hemos adaptado para contenido educativo, añadiendo ejemplos de código C# paso a paso y consejos prácticos útiles.
¿Qué es yield en C#? En palabras sencillas
yield es una palabra clave contextual en C# que le indica al compilador: "Este método es un iterador". Cuando escribes yield return, el método no se ejecuta todo de una vez. En cambio, devuelve los elementos uno por uno, cuando el código que llama los solicita. Esto se llama ejecución perezosa.
A diferencia del return normal, que finaliza el método, yield return pausa el método, guarda su estado y devuelve el elemento actual. En la siguiente llamada, el método continúa desde donde se detuvo.
Sintaxis: yield return y yield break
En C# hay dos formas de yield:
- yield return — devuelve el siguiente elemento de la secuencia.
- yield break — finaliza inmediatamente la iteración (como
breaken un bucle).
El método que usa yield debe devolver uno de estos tipos:
IEnumerable<T>IEnumerableIAsyncEnumerable<T>(a partir de C# 8.0)IEnumerator<T>oIEnumerator
Primer ejemplo: de lo simple a lo complejo
Escribamos un iterador muy simple que genere números del 1 al 5:
public static IEnumerable<int> GetNumbers(){ yield return 1; yield return 2; yield return 3; yield return 4; yield return 5;}
// Uso:foreach (int number in GetNumbers()){ Console.WriteLine(number);}Salida:
12345A primera vista, esto se parece a una lista normal. Pero la diferencia es enorme: cada elemento se genera solo cuando foreach lo solicita. ¡Nada de asignar memoria para toda la lista!
¿Cómo funciona internamente?
El compilador de C# convierte el método con yield en un autómata de estados (state machine) completo. Crea una clase que implementa IEnumerable<T> e IEnumerator<T>. Esta clase almacena:
- El estado actual (en qué línea del método nos detuvimos).
- Las variables locales del método.
- Una referencia al objeto actual (
this), si el método no es estático.
Cada vez que se llama a MoveNext() (y esto lo hace foreach), el autómata de estados reanuda la ejecución desde donde se detuvo por última vez.
Ejemplo real: lectura de un archivo grande
Imagina que necesitas procesar un archivo de registros de 10 GB. Si lo lees completo en memoria, el programa fallará con un OutOfMemoryException. Con yield puedes leer el archivo línea por línea y procesar cada línea sobre la marcha:
public static IEnumerable<s