yield-Anweisung: Geben Sie das nächste Element an

Verwenden Sie die yield-Anweisung in einem Iterator, um den nächsten Wert bereitzustellen oder das Ende einer Iteration zu signalisieren. Die yield-Anweisung weist die folgenden zwei Formen auf:

  • yield return, um den nächsten Wert in der Iteration bereitzustellen, wie das folgende Beispiel zeigt:

    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, um das Ende der Iteration explizit zu signalisieren, wie das folgende Beispiel zeigt:

    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;
            }
        }
    }
    

    Die Iteration wird auch abgeschlossen, wenn das Steuerelement das Ende eines Iterators erreicht.

In den vorherigen Beispielen ist IEnumerable<T> der Rückgabetyp von Iteratoren (in nicht generischen Fällen verwenden Sie IEnumerable als Rückgabetyp eines Iterators). Sie können auch IAsyncEnumerable<T> als Rückgabetyp eines Iterators verwenden. Dadurch wird ein Iterator asynchron. Verwenden Sie die await foreach-Anweisung, um das Ergebnis des Iterators zu durchlaufen, wie das folgende Beispiel zeigt:

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;
}

Der Rückgabetyp eines Iterators kann auch IEnumerator<T> oder IEnumerator sein. Verwenden Sie diese Rückgabetypen, wenn Sie die GetEnumerator-Methode in den folgenden Szenarien implementieren:

  • Sie entwerfen den Typ, der die IEnumerable<T>- oder IEnumerable-Schnittstelle implementiert.

  • Sie fügen die Instanz- oder Erweiterungs-Methode GetEnumerator hinzu, um die Iteration über die Instanz des Typs mit der foreach-Anweisung zu aktivieren, wie das folgende Beispiel zeigt:

    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;
        }
    }
    

Sie können die yield-Anweisungen hierin nicht verwenden:

  • Methoden mit in-, ref- oder out-Parametern
  • Lambdaausdrücke und anonyme Methoden
  • unsichere Blöcke. Vor C# 13 war yield in jeder Methode mit einem unsafe-Block ungültig. Ab C# 13 können Sie yield in Methoden mit unsafe-Blöcken, aber nicht im unsafe-Block verwenden.
  • yield return und yield break kann nicht in Blöcken vom Typ try, catch und finally verwendet werden.

Ausführung eines Iterators

Der Aufruf eines Iterators wird nicht sofort ausgeführt, wie das folgende Beispiel zeigt:

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.

Wie das vorherige Beispiel zeigt, wird beim Durchlaufen des Ergebnisses eines Iterators ein Iterator ausgeführt, bis die erste yield return-Anweisung erreicht wird. Anschließend wird die Ausführung eines Iterators angehalten, und der Aufrufer ruft den ersten Iterationswert ab und verarbeitet ihn. Bei jeder nachfolgenden Iteration wird die Ausführung eines Iterators nach der yield return-Anweisung wieder aufgenommen, die die vorherige Unterbrechung verursacht hat, und fortgesetzt, bis die nächste yield return-Anweisung erreicht ist. Die Iteration wird abgeschlossen, wenn das Steuerelement das Ende eines Iterators oder einer yield break-Anweisung erreicht.

C#-Sprachspezifikation

Weitere Informationen finden Sie im Abschnitt Die yield-Anweisung der C#-Sprachspezifikation.

Weitere Informationen