Introducción a Yield en C# - Qué es, cómo se usa y cuándo es útil
Cuando se encuentra por primera vez la palabra clave yield en C#, puede parecer desconcertante. ¿Cómo funciona exactamente? ¿Cuándo se debe utilizar yield return en lugar de una sentencia return tradicional? Para conseguir una comprensión completa, vamos a recorrer una explicación detallada basada en el excelente tutorial de Tim Corey en YouTube: "Intro to Yield in C# - What it is, how to use it, and when it is useful"
En esta guía, haremos referencia a puntos específicos del vídeo de Tim por marca de tiempo para facilitar la navegación e incluiremos ejemplos prácticos para mostrar cómo yield transforma la forma en que se manejan los flujos de datos, las grandes colecciones y la evaluación perezosa.
Introducción a la palabra clave Yield en C
Tim comienza presentando la palabra clave yield y destacando que a menudo puede resultar confusa para los desarrolladores que se encuentran con ella por primera vez. Explica que la sentencia yield permite a un método detener la ejecución, mantener su estado y continuar desde donde lo dejó en la siguiente llamada. Tim subraya que entender el rendimiento es fundamental para procesar datos de forma eficiente, sobre todo cuando se trata de grandes conjuntos de datos o se implementa una lógica de iteración personalizada.
Configuración de un ejemplo sencillo: Clase Programa y Static Void Main
Para eliminar distracciones, Tim crea una sencilla aplicación de consola en Visual Studio, llamada "YieldDemoApp"

Qué hace realmente Yield en C
A continuación, Tim profundiza en la teoría. En el minuto 2:04, describe el comportamiento de yield: En lugar de procesar toda una colección a la vez, la sentencia yield retiene un punto -como meter un pulgar en un libro- para que la ejecución pueda pausarse y reanudarse más tarde.
Este comportamiento es crucial para la ejecución diferida, en la que los valores se generan solo cuando se necesitan, en lugar de precomputar todo por adelantado. La descripción de Tim sienta claramente las bases para comprender cómo funciona el retorno de rendimiento.
Escribir código de demostración
En el método estático void Main de la clase Program, establece mensajes básicos como "Inicio de la aplicación" y "Fin de la aplicación" utilizando Console.WriteLine, lo que ayuda a visualizar el flujo claramente cuando se trabaja con un bucle foreach más adelante.
Este ejemplo de código inicial se centra únicamente en la implementación del rendimiento, sin incluir complejidades de interfaz de usuario.
Creación de la clase PersonModel
Para demostrarlo, Tim crea una clase PersonModel con las propiedades FirstName y LastName y un constructor. Cuando se crea un objeto PersonModel, se imprime un mensaje que indica qué usuario se ha inicializado. Esto ayuda a visualizar cuándo se crean los objetos y cuándo se consumen.

Este sencillo paso de generación de código prepara el terreno para trabajar con iteradores personalizados.
Construyendo la clase DataAccess con un retorno de lista tradicional
En el minuto 5:06, Tim pasa a una clase DataAccess con un método GetPeople que devuelve IEnumerable

Este método iterador carga todos los objetos inmediatamente en memoria antes de que comience la iteración - un punto importante que Tim contrasta más adelante con el uso de yield.

Demostración del uso de memoria con List
Después de ejecutar el bucle foreach, Tim muestra que los tres usuarios se crean antes incluso de que se lea el primer elemento. Esto pone de relieve un problema potencial cuando se trabaja con grandes colecciones o grandes conjuntos de datos: el elevado uso de memoria.

Tim explica que si tuviéramos, digamos, mil usuarios, habríamos creado mil objetos aunque solo necesitáramos unos pocos, lo que daría lugar a una asignación ineficiente de memoria.
Cambiando a Rendimiento por Ejecución Diferida
En el minuto 10:01, Tim modifica GetPeople para utilizar yield return en lugar de crear una colección temporal (una List). Cada sentencia yield return emite directamente un PersonModel cada vez.
Este código crítico dentro del método permite la evaluación perezosa, donde el siguiente elemento se genera sólo cuando es necesario por el foreach.
Tim también aclara que el tipo de retorno del método debe ser IEnumerable
Debugging: Cómo el compilador genera código para el rendimiento
Tim utiliza puntos de interrupción para explicar el proceso paso a paso. Muestra que antes de la primera llamada a MoveNext, la secuencia está vacía. Sólo cuando foreach necesita el siguiente valor, el compilador activa el bloque iterador y ejecuta la línea yield return num, que inicializa y devuelve el PersonModel.

Tim destaca que el compilador genera máquinas de estado especiales bajo el capó para gestionar la ejecución pausada y reanudada.
Beneficios y eficacia del uso de Yield Return
Tim explica por qué el rendimiento es tan eficaz:
-
Puede buscar un registro cada vez.
-
Puede limitar el número de int que necesita.
-
Se evita cargar toda la colección en la memoria.
- Se mejora la escalabilidad, especialmente cuando se trabaja con procesamiento de archivos o flujos de datos de gran tamaño.
Mediante el uso de .Take(2) de LINQ, Tim demuestra cómo sólo se inicializan dos objetos a pesar de que existen tres declaraciones de retorno de rendimiento, lo que pone de relieve la ejecución diferida en acción.
Ejemplo práctico: Generador de números primos usando Yield
Tim crea un nuevo método estático IEnumerable
Este ejemplo muestra que el rendimiento es fundamental para manejar secuencias infinitas con seguridad.
Sin rendimiento, el código se bloquearía debido al consumo ilimitado de memoria. Sin embargo, al utilizar yield, la ejecución diferida garantiza que los números se generen bajo demanda.
Obtención de números primos con Take()
A continuación, Tim muestra cómo obtener de forma segura sólo un número fijo de números primos:
var primeNumbers = Generators.GetPrimeNumbers().Take(10000);
var primeNumbers = Generators.GetPrimeNumbers().Take(10000);
Este código escrito dentro del void estático Main obtiene eficientemente sólo 10.000 primos.
Dado que el retorno de rendimiento produce números uno a uno, el uso de memoria se mantiene bajo.
Iteración personalizada: Usando GetEnumerator y MoveNext
Tim va más allá y explica cómo controlar la iteración manualmente.
Crea un var iterator = primeNumbers.GetEnumerator(), luego utiliza un bucle for con int i y llama a iterator.MoveNext() para obtener elementos manualmente.
Este enfoque manual permite una iteración personalizada, preguntando por el siguiente valor sólo cuando sea necesario y mostrando que el método se reanuda exactamente desde donde se detuvo por última vez.
Un error común: El uso de ToList() en colecciones cedidas
En el minuto 36:45, Tim advierte: convertir una secuencia yield en una lista con .ToList() provoca una evaluación completa inmediata.
Si llamas a .ToList() en una secuencia infinita, corres el riesgo de que tu aplicación se bloquee.
Tim subraya que el retorno de rendimiento está pensado para la evaluación perezosa, y que llamar a .ToList() rompe este patrón al forzar la materialización completa en memoria.
Al trabajar con métodos LINQ, Tim recomienda ser cauteloso a la hora de introducir .ToList().
Resumen: Por qué Yield es una herramienta potente
Tim concluye insistiendo en que, si bien la palabra clave yield añade una ligera sobrecarga (al mantener una máquina de estados), las ventajas de un menor uso de memoria y de la evaluación perezosa la convierten en una potente herramienta de trabajo:
-
Grandes conjuntos de datos
-
Archivos de gran tamaño
-
Iteradores personalizados
-
Secuencias infinitas
-
Flujos de datos
- Tratamiento diferido
Sugiere practicar con el rendimiento en diferentes proyectos para desarrollar una comprensión más profunda del rendimiento y evitar errores comunes.
Tim concluye invitando a los espectadores a compartir cómo han utilizado el rendimiento en el código de producción.
Conclusión
Al recorrer el vídeo de Tim Corey, hemos visto las inmensas ventajas que la palabra clave yield aporta a C#. Desde la creación de iteradores personalizados hasta la gestión eficiente de grandes colecciones, yield return permite que los retornos de función sean más inteligentes y eficientes en términos de memoria. Tanto si trabaja con var number, var point, var reader, var connection o grandes conjuntos de datos, el dominio de yield puede elevar drásticamente sus habilidades de codificación en C#.
Si no has explorado antes el uso de yield, ahora es el momento perfecto para practicarlo a través de ejemplos sencillos y comprender mejor cómo funciona el retorno yield dentro del compilador de C#. Echa un vistazo al canal oficial de Tim YouTube Channel para ver más vídeos interesantes.
