Freigeben über


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

Verwenden Sie die yield Anweisung in einem Iterator , um den nächsten Wert anzugeben 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.

Die C#-Sprachreferenz dokumentiert die zuletzt veröffentlichte Version der C#-Sprache. Außerdem enthält sie erste Dokumentation für Features in der öffentlichen Vorschau für die kommende Sprachversion.

In der Dokumentation werden alle Features identifiziert, die in den letzten drei Versionen der Sprache oder in der aktuellen öffentlichen Vorschau eingeführt wurden.

Tipp

Informationen dazu, wann ein Feature erstmals in C# eingeführt wurde, finden Sie im Artikel zum Versionsverlauf der C#-Sprache.

In den vorherigen Beispielen lautet IEnumerable<T>der Rückgabetyp der Iteratoren . Verwenden Sie IEnumerable in nichtgenerischen Fällen 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.
  • Lambda-Ausdrü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 Catch - und schließlich-Blöcken oder in Try-Blocks mit einem entsprechenden catch Block verwendet werden. Die yield return Anweisungen und yield break Anweisungen können in einem try Block ohne catch Blöcke verwendet werden, nur einen finally Block.

using Anweisungen in Iteratoren

Sie können Anweisungen in Iteratormethoden verwendenusing. Da using Anweisungen in try Blöcke mit finally Klauseln (und ohne catch Blöcke) kompiliert werden, funktionieren sie ordnungsgemäß mit Iteratoren. Die verfügbaren Ressourcen werden während der gesamten Ausführung des Iterators ordnungsgemäß verwaltet:

Console.WriteLine("=== Using in Iterator Example ===");

// Demonstrate that using statements work correctly in iterators
foreach (string line in ReadLinesFromResource())
{
    Console.WriteLine($"Read: {line}");
    // Simulate processing only first two items
    if (line == "Line 2") break;
}

Console.WriteLine("Iteration stopped early - resource should still be disposed.");

static IEnumerable<string> ReadLinesFromResource()
{
    Console.WriteLine("Opening resource...");
    using var resource = new StringWriter(); // Use StringWriter as a simple IDisposable
    resource.WriteLine("Resource initialized");
    
    // These lines would typically come from the resource (e.g., file, database)
    string[] lines = { "Line 1", "Line 2", "Line 3", "Line 4" };
    
    foreach (string line in lines)
    {
        Console.WriteLine($"About to yield: {line}");
        yield return line;
        Console.WriteLine($"Resumed after yielding: {line}");
    }
    
    Console.WriteLine("Iterator completed - using block will dispose resource.");
}

Wie im vorherigen Beispiel gezeigt, bleibt die in der using Anweisung erworbene Ressource während der gesamten Ausführung des Iterators verfügbar, auch wenn der Iterator die Ausführung an yield return Anweisungen anhält und fortsetzt. Die Ressource wird verworfen, wenn der Iterator abgeschlossen ist (entweder durch Erreichen des Endes oder über yield break) oder wenn der Iterator selbst verworfen wird (z. B. wenn der Aufrufer frühzeitig aus der Enumeration ausbricht).

Ausführung eines Iterators

Wenn Sie einen Iterator aufrufen, wird er nicht sofort ausgeführt, wie im folgenden Beispiel gezeigt:

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 im vorherigen Beispiel gezeigt, wird der Iterator ausgeführt, bis die erste yield return Anweisung erreicht ist, wenn Sie mit dem Durchlaufen des Ergebnisses eines Iterators beginnen. Anschließend wird die Ausführung des Iterators angehalten, und der Aufrufer ruft den ersten Iterationswert ab und verarbeitet ihn. Bei jeder nachfolgenden Iteration wird die Ausführung des Iterators nach der yield return Anweisung fortgesetzt, die die vorherige Unterbrechung verursacht hat, und wird 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