Uso de yield en C#, ese pequeño desconocido
La palabra clave contextual yield (no palabra reservada) tiene un uso y significado muy concreto en C#.
Nota: puedes acceder a la lista de palabras clave contextuales de C# en este enlace
El proceso de trabajo con yield es muy simple y al mismo tiempo muy beneficioso, sin embargo, es un gran desconocido por muchos programadores de C# y está infrautilizado.
Quizás porque su funcionamiento es algo extraño.
Espero que con esta entrada quede mucho más claro cómo funciona y nos aclare sus ventajas.
Con yield estaremos indicando que el get, método u operador que accede al mismo, es un iterador.
De esta forma, devolveremos cada valor de la iteración de uno en uno a través de return yield.
La función que contiene cada return yield, debe devolver un objeto que implemente la interfaz IEnumerable<object>.
Para acceder a sus elementos, bastará con hacerlo dentro de un bucle foreach y llamando al método o bien accediendo a ellos a través de una consulta LINQ.
En cada iteración del bucle foreach llamamos al método tal y como indico.
Al ejecutarse return yield dentro del método, se devuelve el valor de la expresión y se guarda internamente la localización del código que ejecutaba la iteración, de manera tal que la siguiente iteración o llamada al método pasa directamente al elemento siguiente.
En el caso de que queramos detener la iteración, bastará con indicar yield break.
Veamos el uso de yield con un sencillo ejemplo que ayudará a comprender mejor su uso.
Imaginemos el siguiente código de nuestro método encargado de devolver el nombre de los doce meses del año en un idioma concreto.
private static IEnumerable GetMonthNames(string culture) { for (int i = 0; i < 12; i++) yield return new DateTime(2020, i + 1, 1) .ToString("MMMM", CultureInfo.CreateSpecificCulture(culture)); }
Ahora imaginemos la parte del código que se encarga de iterar por los elementos que devuelve el método y que irá mostrando en pantalla uno a uno.
foreach (var monthName in GetMonthNames("en")) Console.WriteLine($"{monthName}");
También podríamos utilizar LINQ para recorrer los elementos y obtener aquellos que siguen la lógica de LINQ establecida.
Imaginemos por ejemplo que queremos obtener aquellos meses que empiezan por la letra «M».
Nuestro código quedaría de la siguiente forma en ese caso:
foreach (var monthName in GetMonthNames("en").Cast<string>().Where(q => q.StartsWith("M"))) Console.WriteLine($"{monthName}");
Cabe recordar también, que el método puede ser llamado en cualquier momento como lo hacemos siempre con un método que devolviera una colección sin atender a cada uno de los resultados devueltos en yield, por lo que podríamos hacer algo parecido a:
var monthNames = GetMonthNames("es");
monthNames será en este caso de tipo IEnumerable.
Como podemos apreciar en los ejemplos que hemos visto, lo que hacemos es utilizar un bucle foreach para recorrer todos los elementos que nos devuelva el método GetMonthNames.
En cada iteración llamaremos al método que irá devolviendo uno a uno los elementos pedidos hasta que finalice el bucle dentro del método.
De esta forma, con yield podremos ejecutar o realizar acciones adicionales con cada uno de los elementos sin tener que esperar a que el método se termine de ejecutar y devolver todos los elementos que contendría la colección tal y como normalmente utilizamos las colecciones en nuestras aplicaciones.
Una ventaja adicional de usar yield es que nos evitaremos la creación de colecciones temporales o variables adicionales para ir recopilando y creando un conjunto de resultados.
Pero hay más ventajas de usar yield como el hecho de ir obteniendo la respuesta de cada valor antes de que todos sus valores hayan sido devueltos y por lo tanto, podemos ir realizando acciones o tareas adicionales por cada uno.
Espero que por un lado, haya desmitificado un poco yield, y por otro lado haya podido valorar las ventajas que su uso puede darnos en determinados contextos.
Como información extra, te invito a visitar la entrada que publiqué hace algunos meses respecto a Asynchronous Streams dónde se usa la interfaz IAsyncEnumerable<T>.
Happy Coding!
2 Responsesso far
Acerca de «También podríamos utilizar LINQ para recorrer los elementos y obtener aquellos que siguen la lógica de LINQ establecida.»
using System.Linq;
….
‘IEnumerable’ does not contain a definition for ‘Where’
¿?
Hola Andrea
Aunque siempre preparo bien todo lo que subo, el código efectivamente, tenía un typo que ya he corregido. O:-)
foreach (var monthName in GetMonthNames(«en»).Where(q => q.StartsWith(«M»)))
Console.WriteLine($»{monthName}»);
Debería ser:
foreach (var monthName in GetMonthNames(«en»).Cast<string>().Where(q => q.StartsWith(«M»)))
Console.WriteLine($»{monthName}»);
No obstante, el código anterior:
foreach (var monthName in GetMonthNames(«en»).Where(q => q.StartsWith(«M»)))
Console.WriteLine($»{monthName}»);
También es válido si la función que usaba de ejemplo fuera (devolviendo un IEnumerable<string> en lugar de un IEnumerable):
private static IEnumerable<string> GetMonthNames(string culture)
{
for (int i = 0; i < 12; i++)
yield return new DateTime(2020, i + 1, 1)
.ToString("MMMM", CultureInfo.CreateSpecificCulture(culture));
}
En algún momento, o mis dedos han cambiado algo, o la parte <string> me la ha quitado el corrector del blog, que a veces lo hace, y no me he dado cuenta.
Gracias por comentar,
Jorge