Todo lo que necesitas saber sobre Yield en C#
Mejora la eficiencia de memoria al generar secuencias bajo demanda con yield en C# y programa como todo un pro 🐿️

Hey devs, en esta ocasión aprenderemos sobre la instrucción Yield, lo he visto en varias entrevistas técnicas así que si no sabes qué es aún, no te preocupes, que de aquí saldrás sabiendo 🐿️🤭.

Cuando estamos programando, muchas veces necesitamos trabajar con colecciones de datos, como listas o arreglos, que pueden ser bastante grandes. Imagina que tienes una lista de miles de números, pero solo te interesa procesar uno a la vez, ahora imagina que quieres hacerlo de forma eficiente. Aquí es donde entra en juego una palabra clave muy útil de C#: yield.

Esta instrucción nos permite devolver elementos uno por uno sin tener que cargar toda la colección en memoria. Esto se conoce como "iteración perezosa" o "lazy loading", ya que los elementos solo se generan cuando los necesitamos. Comencemos devs! ✌🏼

Definición de yield

Es una palabra clave que se usa en C# dentro de métodos iteradores, que son métodos que devuelven una secuencia de elementos de uno en uno. Estos métodos no devuelven toda la colección de golpe; en cambio, pausan su ejecución en cada yield y luego la reanudan cuando se piden más elementos.

Cuando escribimos un método con yield return, estamos creando una secuencia de datos que podemos recorrer sin tener que almacenar todos los elementos al mismo tiempo. Esto es muy útil cuando estamos tratando con grandes cantidades de datos o cuando solo necesitamos una parte de una lista.

Cómo se usa

El uso básico de yield implica dos cosas:

  1. yield return: Para devolver un valor dentro de un ciclo o de un método iterador.
  2. yield break: Para terminar la secuencia de datos y señalar que no habrá más valores.

Ejemplo sencillo: Generando números pares

Vamos a crear un ejemplo donde generamos números pares dentro de un rango de manera eficiente, es decir, solo los generamos cuando se necesitan. Aquí es donde yield es útil.

Código con yield return:

public IEnumerable<int> NumerosPares(int limite)
{
    for (int i = 0; i <= limite; i += 2)
    {
        yield return i; //Aquí devuelvo el número par
    }
}

¿Qué pasa aquí?

  • IEnumerable<int>: Este tipo nos indica que el método va a devolver una secuencia de números enteros, pero de manera perezosa, es decir, uno por uno.
  • yield return i: Cada vez que se llama al método, se devuelve un número par y se "pausa" la ejecución hasta que se pida el siguiente número. Cuando el bucle se termine, el método dejará de devolver más números.

Y sin yield cómo quedaría ese método?

Sin yield tendrías que crear un objeto de tipo Lista para almacenar los resultados de la iteración para luego retornarla como respuesta, así:

public List<int> NumerosPares(int limite)
{
    List<int> numerosPares = new List<int>(); //Crear una lista para almacenar los números pares
    for (int i = 0; i <= limite; i += 2)
    {
        numerosPares.Add(i); //Añadimos cada número par a la lista
    }
    return numerosPares; //Devolvemos la lista completa
}

Notas ahora la diferencia? 🐿️✌🏼Con yield es mucho mejor.

Ahora, veamos cómo podrías utilizar el método en un programa:

foreach (var numero in GenerarNumerosPares(12))
{
    Console.WriteLine(numero);  //Aquí imprime 0, 2, 4, 6, 8, 10, 12
}

Cada vez que el foreach avanza, el método GenerarNumerosPares devuelve el siguiente número par de la secuencia. Como puedes ver, no estamos almacenando todos los números en una lista, solo generamos uno cuando lo necesitamos.

Y por qué debería usar yield?

  1. Eficiencia de memoria: Con yield, solo generamos los elementos cuando los pedimos. Esto es mucho más eficiente que almacenar todos los elementos en memoria si solo necesitamos una parte de la lista.
  2. Control de flujo: Usar yield hace que el código sea más claro y fácil de mantener. En lugar de construir una lista completa de números y devolverla, puedes simplemente devolver un valor a la vez y dejar que el consumidor decida qué hacer con esos valores.
  3. Performance en colecciones grandes: Cuando trabajamos con colecciones grandes (como miles de elementos), usar yield evita que el programa tenga que cargar todos los elementos en memoria a la vez, lo que puede ser costoso en términos de tiempo y espacio.

Y qué hay de yield break?

Como mencioné antes, también podemos usar yield break para indicar que la secuencia ha terminado. Esto es útil cuando queremos detener la iteración antes de que se procesen todos los elementos.

Ejemplo de yield break:

Supón que tienes una lista de comentarios y quieres filtrar los comentarios hasta encontrar un comentario con la palabra "fin", lo cual indica que el hilo ha terminado. Un código optimizado usando yield y yield break luciría así:

    public static IEnumerable<string> FiltrarComentarios(List<string> comentarios, string palabraClave)
    {
        foreach (var comentario in comentarios)
        {
            if (comentario.Contains(palabraClave)) 
            {
                yield break;  //Termina la secuencia cuando encuentra la palabra clave
            }
            yield return comentario;
        }
    }

En este caso, el método devuelve todas las palabras hasta que encuentra la palabraClave. En cuanto la encuentra, se detiene gracias a yield break y no devuelve más elementos, en este caso comentarios.

En este método si en lugar de yield break; sólo pones break; te darás cuenta que funcionalmente nada ha cambiado, el código seguiría funcionando bien, sin embargo, internamente no es correcto y no es lo mismo ya que se pierde eficiencia. Estas cosas hacen la diferencia entre un developer normalito y uno excepcional, toma nota e investiga este interesante caso, tú eres un crack 🙌

Conclusiones 🐿️🙌

El uso de yield en C# nos permite crear métodos iteradores que devuelven elementos uno por uno, de manera eficiente y sin necesidad de almacenar toda la colección en memoria.

Con yield return, podemos crear secuencias de datos que se generan solo cuando las necesitamos, lo que mejora el rendimiento de nuestros programas. Y con yield break, podemos finalizar esas secuencias cuando queramos, proporcionando un control más fino sobre cómo manejamos los datos.

Entonces estimado dev, como habrás notado, yield es una herramienta poderosa que puede optimizar el uso de memoria y mejorar la legibilidad de tu código, especialmente cuando trabajas con colecciones grandes o secuencias que no necesitan ser completamente cargadas de antemano.

Estoy seguro que esta entrada te ha gustado, entonces compártela, eso ayuda un montón crack 🥳

Créditos de imagen de portada: Foto de Alex Kotliarskyi en Unsplash

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *