Instrucción yield: proporcione el siguiente elemento.
Use la instrucción yield
en un iterador para proporcionar el siguiente valor o indicar el final de la iteración. La instrucción yield
tiene las dos formas siguientes:
yield return
: para proporcionar el siguiente valor en la iteración, como se muestra en el siguiente ejemplo:foreach (int i in ProduceEvenNumbers(9)) { Console.Write(i); Console.Write(" "); } // Output: 0 2 4 6 8 IEnumerable<int> ProduceEvenNumbers(int upto) { for (int i = 0; i <= upto; i += 2) { yield return i; } }
yield break
: para indicar explícitamente el final de la iteración, como se muestra en el siguiente ejemplo:Console.WriteLine(string.Join(" ", TakeWhilePositive(new int[] {2, 3, 4, 5, -1, 3, 4}))); // Output: 2 3 4 5 Console.WriteLine(string.Join(" ", TakeWhilePositive(new int[] {9, 8, 7}))); // Output: 9 8 7 IEnumerable<int> TakeWhilePositive(IEnumerable<int> numbers) { foreach (int n in numbers) { if (n > 0) { yield return n; } else { yield break; } } }
La iteración finaliza también cuando el control llega al final de un iterador.
En los ejemplos anteriores, el tipo de valor devuelto de los iteradores es IEnumerable<T> (en casos no genéricos, use IEnumerable como tipo de valor devuelto de un iterador). También se puede usar IAsyncEnumerable<T> como tipo de valor devuelto de un iterador. Esto hace que el iterador sea asincrónico. Use la instrucción await foreach
para iterar el resultado del iterador, como se muestra en el siguiente ejemplo:
await foreach (int n in GenerateNumbersAsync(5))
{
Console.Write(n);
Console.Write(" ");
}
// Output: 0 2 4 6 8
async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
{
for (int i = 0; i < count; i++)
{
yield return await ProduceNumberAsync(i);
}
}
async Task<int> ProduceNumberAsync(int seed)
{
await Task.Delay(1000);
return 2 * seed;
}
IEnumerator<T> o IEnumerator pueden ser también el tipo de valor devuelto de un iterador. Use tipos de valor devuelto cuando implemente el método GetEnumerator
en los siguientes escenarios:
Al diseñar el tipo que implementa la interfaz IEnumerable<T> o IEnumerable.
Al agregar una instancia o un método de extensión
GetEnumerator
para habilitar la iteración por la instancia del tipo con la instrucciónforeach
, como se muestra en el siguiente ejemplo:public static void Example() { var point = new Point(1, 2, 3); foreach (int coordinate in point) { Console.Write(coordinate); Console.Write(" "); } // Output: 1 2 3 } public readonly record struct Point(int X, int Y, int Z) { public IEnumerator<int> GetEnumerator() { yield return X; yield return Y; yield return Z; } }
Las yield
instrucciones no pueden usarse en:
- Métodos con parámetros in, ref o out
- Expresiones lambda y métodos anónimos
- Bloques no seguros. Antes de C# 13,
yield
no era válido en ningún método con un bloqueunsafe
. A partir de C# 13, puede usaryield
en métodos con bloquesunsafe
, pero no en el bloqueunsafe
. yield return
yyield break
no se pueden usar en bloques try, catch y finally.
Ejecución de un iterador
La llamada de un iterador no hace que se ejecute de inmediato, como se muestra en el siguiente ejemplo:
var numbers = ProduceEvenNumbers(5);
Console.WriteLine("Caller: about to iterate.");
foreach (int i in numbers)
{
Console.WriteLine($"Caller: {i}");
}
IEnumerable<int> ProduceEvenNumbers(int upto)
{
Console.WriteLine("Iterator: start.");
for (int i = 0; i <= upto; i += 2)
{
Console.WriteLine($"Iterator: about to yield {i}");
yield return i;
Console.WriteLine($"Iterator: yielded {i}");
}
Console.WriteLine("Iterator: end.");
}
// Output:
// Caller: about to iterate.
// Iterator: start.
// Iterator: about to yield 0
// Caller: 0
// Iterator: yielded 0
// Iterator: about to yield 2
// Caller: 2
// Iterator: yielded 2
// Iterator: about to yield 4
// Caller: 4
// Iterator: yielded 4
// Iterator: end.
Como se aprecia en el ejemplo anterior, cuando se empieza a iterar por el resultado de un iterador, se ejecuta un iterador hasta que se alcanza la primera instrucción yield return
. Tras ello, se suspende la ejecución de un iterador, el autor de la llamada obtiene el primer valor de iteración y lo procesa. En cada iteración posterior, la ejecución de un iterador se reanuda después de la instrucción yield return
que provocó la suspensión anterior, y continúa hasta que se alcanza la siguiente instrucción yield return
. La iteración se completa cuando el control llega al final de un iterador o una instrucción yield break
.
Especificación del lenguaje C#
Para obtener más información, vea la sección La declaración yield de Especificación del lenguaje C#.