IteratorenIterators

Bei fast jedem Programm, das Sie schreiben, muss eine Auflistung durchlaufen werden.Almost every program you write will have some need to iterate over a collection. Sie schreiben Code, der jedes Element in einer Auflistung überprüft.You'll write code that examines every item in a collection.

Sie erstellen auch Iteratormethoden, die einen Iterator für die Elemente dieser Klasse erzeugen.You'll also create iterator methods which are methods that produces an iterator for the elements of that class. Diese können für Folgendes verwendet werden:These can be used for:

  • Ausführen einer Aktion für jedes Element in einer AuflistungPerforming an action on each item in a collection.
  • Enumerieren einer benutzerdefinierten AuflistungEnumerating a custom collection.
  • Erweitern von LINQ oder anderen BibliothekenExtending LINQ or other libraries.
  • Erstellen einer Datenpipeline, in der Daten Iteratormethoden effizient durchlaufenCreating a data pipeline where data flows efficiently through iterator methods.

Die Programmiersprache C# bietet Funktionen für diese beiden Szenarien.The C# language provides features for both these scenarios. Dieser Artikel enthält eine Übersicht über diese Funktionen.This article provides an overview of those features.

Dieses Tutorial besteht aus vielen Schritten.This tutorial has multiple steps. Sie können die Anwendung nach jedem Schritt ausführen und sich den Fortschritt ansehen.After each step, you can run the application and see the progress. Hier können Sie das vollständige Beispiel für dieses Thema anzeigen oder herunterladen.You can also view or download the completed sample for this topic. Anweisungen zum Herunterladen finden Sie unter Beispiele und Lernprogramme.For download instructions, see Samples and Tutorials.

Durchlaufen mit foreachIterating with foreach

Enumerieren einer Auflistung ist einfach: Das foreach-Schlüsselwort enumeriert eine Auflistung und führt die eingebettete Anweisung einmal für jedes Element in der Auflistung aus:Enumerating a collection is simple: The foreach keyword enumerates a collection, executing the embedded statement once for each element in the collection:

foreach (var item in collection)
{
   Console.WriteLine(item.ToString());
}

Das ist auch schon alles.That's all there is to it. Für das Durchlaufen aller Inhalte einer Auflistung benötigen Sie nur die foreach-Anweisung.To iterate over all the contents of a collection, the foreach statement is all you need. Die foreach-Anweisung ist jedoch nicht magisch.The foreach statement isn't magic, though. Sie beruht auf zwei generischen Schnittstellen, die in der .NET Core-Bibliothek definiert sind, um den Code für das Durchlaufen einer Auflistung zu generieren: IEnumerable<T> und IEnumerator<T>.It relies on two generic interfaces defined in the .NET core library in order to generate the code necessary to iterate a collection: IEnumerable<T> and IEnumerator<T>. Dieser Mechanismus wird unten ausführlich erläutert.This mechanism is explained in more detail below.

Für diese beiden Schnittstellen gibt es auch nicht generische Entsprechungen: IEnumerable und IEnumerator.Both of these interfaces also have non-generic counterparts: IEnumerable and IEnumerator. Die generischen Versionen werden für modernen Code bevorzugt.The generic versions are preferred for modern code.

Enumerationsquellen mit IteratormethodenEnumeration sources with iterator methods

Mit einer weiteren großartigen Funktion der Programmiersprache C# können Sie Methoden konstruieren, die eine Quelle für eine Enumeration erstellen.Another great feature of the C# language enables you to build methods that create a source for an enumeration. Diese werden als Iteratormethoden bezeichnet.These are referred to as iterator methods. Eine Iteratormethode definiert, wie die Objekte in einer Sequenz bei Anforderung generiert werden.An iterator method defines how to generate the objects in a sequence when requested. Sie verwenden die yield return-Kontextschlüsselwörter, um eine Iteratormethode zu definieren.You use the yield return contextual keywords to define an iterator method.

Sie könnten diese Methode schreiben, um die Sequenz von ganzen Zahlen von 0 bis 9 zu erstellen:You could write this method to produce the sequence of integers from 0 through 9:

public IEnumerable<int> GetSingleDigitNumbers()
{
    yield return 0;
    yield return 1;
    yield return 2;
    yield return 3;
    yield return 4;
    yield return 5;
    yield return 6;
    yield return 7;
    yield return 8;
    yield return 9;
}

Der obige Code zeigt verschiedene yield return-Anweisungen, die verdeutlichen, dass Sie mehrere diskrete yield return-Anweisungen in einer Iteratormethode verwenden können.The code above shows distinct yield return statements to highlight the fact that you can use multiple discrete yield return statements in an iterator method. Sie können (und werden auch oft) andere Sprachkonstrukte zur Vereinfachung des Codes einer Iteratormethode verwenden.You can (and often do) use other language constructs to simplify the code of an iterator method. Die folgende Methodendefinition erzeugt genau dieselbe Sequenz von Zahlen:The method definition below produces the exact same sequence of numbers:

public IEnumerable<int> GetSingleDigitNumbers()
{
    int index = 0;
    while (index++ < 10)
        yield return index;
}

Sie müssen sich nicht für eine davon entscheiden.You don't have to decide one or the other. Sie können so viele yield return-Anweisungen verwenden wie für Ihre Methode erforderlich:You can have as many yield return statements as necessary to meet the needs of your method:

public IEnumerable<int> GetSingleDigitNumbers()
{
    int index = 0;
    while (index++ < 10)
        yield return index;

    yield return 50;

    index = 100;
    while (index++ < 110)
        yield return index;
}

Das ist die grundlegende Syntax.That's the basic syntax. Betrachten wir einen realen Beispielfall, in dem Sie eine Iteratormethode schreiben würden.Let's consider a real world example where you would write an iterator method. Angenommen, Sie arbeiten an einem IoT-Projekt und die Gerätesensoren generieren einen sehr großen Datenstrom.Imagine you're on an IoT project and the device sensors generate a very large stream of data. Um ein Gefühl für die Daten zu bekommen, können Sie eine Methode schreiben, die bei jedem n-ten Datenelement eine Stichprobe durchführt.To get a feel for the data, you might write a method that samples every Nth data element. Diese kleine Iteratormethode ist der Trick dabei:This small iterator method does the trick:

public static IEnumerable<T> Sample(this IEnumerable<T> sourceSequence, int interval)
{
    int index = 0;
    foreach (T item in sourceSequence)
    {
        if (index++ % interval == 0)
            yield return item;
    }
}

Es gibt eine wichtige Einschränkung bei Iteratormethoden: Eine return-Anweisung und eine yield return-Anweisung können nicht in derselben Methode enthalten sein.There is one important restriction on iterator methods: you can't have both a return statement and a yield return statement in the same method. Folgendes wird nicht kompiliert:The following will not compile:

public IEnumerable<int> GetSingleDigitNumbers()
{
    int index = 0;
    while (index++ < 10)
        yield return index;

    yield return 50;

    // generates a compile time error: 
    var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
    return items;  
}

Diese Einschränkung ist normalerweise kein Problem.This restriction normally isn't a problem. Sie können in der Methode entweder durchgehend yield return verwenden oder die ursprüngliche Methode in mehrere Methoden aufteilen, von denen einige return und einige yield return verwenden.You have a choice of either using yield return throughout the method, or separating the original method into multiple methods, some using return, and some using yield return.

Sie können die letzte Methode leicht ändern und so yield return überall verwenden:You can modify the last method slightly to use yield return everywhere:

public IEnumerable<int> GetSingleDigitNumbers()
{
    int index = 0;
    while (index++ < 10)
        yield return index;

    yield return 50;

    var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
    foreach (var item in items)
        yield return item;
}

Manchmal ist die beste Lösung, eine Iteratormethode in zwei verschiedene Methoden aufzuteilen.Sometimes, the right answer is to split an iterator method into two different methods. Die eine verwendet dann return und die zweite verwendet yield return.One that uses return, and a second that uses yield return. Betrachten Sie eine Situation, in der Sie eine leere Auflistung oder die ersten 5 ungeraden Zahlen basierend auf einem booleschen Argument zurückgeben möchten.Consider a situation where you might want to return an empty collection, or the first 5 odd numbers, based on a boolean argument. Sie können das mit diesen zwei Methoden schreiben:You could write that as these two methods:

public IEnumerable<int> GetSingleDigitOddNumbers(bool getCollection)
{
    if (getCollection == false)
        return new int[0];
    else
        return IteratorMethod();
}

private IEnumerable<int> IteratorMethod()
{
    int index = 0;
    while (index++ < 10)
        if (index % 2 == 1)
            yield return index;
}

Betrachten Sie die oben genannten Methoden.Look at the methods above. Die erste Methode verwendet die return-Standardanweisung, um entweder eine leere Auflistung oder den Iterator, der durch die zweite Methode erstellt wurde, zurückzugeben.The first uses the standard return statement to return either an empty collection, or the iterator created by the second method. Die zweite Methode verwendet die yield return-Anweisung, um die angeforderte Reihenfolge zu erstellen.The second method uses the yield return statement to create the requested sequence.

Tieferer Einblick in foreachDeeper Dive into foreach

Die foreach-Anweisung wird in einen Standardausdruck erweitert, der die Schnittstellen IEnumable<T> und IEnumerator<T> für das Durchlaufen aller Elemente einer Auflistung verwendet.The foreach statement expands into a standard idiom that uses the IEnumable<T> and IEnumerator<T> interfaces to iterate across all elements of a collection. Außerdem werden Fehler minimiert, die Entwickler durch falsche Ressourcenverwaltung verursachen.It also minimizes errors developers make by not properly managing resources.

Der Compiler übersetzt die im ersten Beispiel gezeigte foreach-Schleife in etwas wie dieses Konstrukt:The compiler translates the foreach loop shown in the first example into something similar to this construct:

IEnumerator<int> enumerator = collection.GetEnumerator();
while (enumerator.MoveNext())
{
    var item = enumerator.Current;
    Console.WriteLine(item.ToString());
}

Das obige Konstrukt stellt den Code dar, der durch den C#-Compiler ab Version 5 und höher generiert wird.The construct above represents the code generated by the C# compiler as of version 5 and above. Vor Version 5 hatte die item-Variable einen anderen Bereich:Prior to version 5, the item variable had a different scope:

// C# versions 1 through 4:
IEnumerator<int> enumerator = collection.GetEnumerator();
int item = default(int);
while (enumerator.MoveNext())
{
    item = enumerator.Current;
    Console.WriteLine(item.ToString());
}

Dies wurde geändert, da das frühere Verhalten zu kleinen und schwierig zu diagnostizierenden Fehlern im Zusammenhang mit Lambdaausdrücken führen konnte.This was changed because the earlier behavior could lead to subtle and hard to diagnose bugs involving lambda expressions. Im Abschnitt Lambdaausdrücke finden Sie weitere Informationen.See the section on lambda expressions for more information.

Der genaue, vom Compiler generierte Code ist etwas komplizierter und behandelt Situationen, in denen das von GetEnumerator() zurückgegebene Objekt die IDisposable-Schnittstelle implementiert.The exact code generated by the compiler is somewhat more complicated, and handles situations where the object returned by GetEnumerator() implements the IDisposable interface. Der durch vollständige Erweiterung generierte Code sieht eher wie folgt aus:The full expansion generates code more like this:

{
    var enumerator = collection.GetEnumerator();
    try 
    {
        while (enumerator.MoveNext())
        {
            var item = enumerator.Current;
            Console.WriteLine(item.ToString());
        }
    } finally 
    {
        // dispose of enumerator.
    }
}

Die Art und Weise, wie der Enumerator verworfen wird, hängt von den Merkmalen des Typs von enumerator ab.The manner in which the enumerator is disposed of depends on the characteristics of the type of enumerator. Im Allgemeinen wird die finally-Klausel wie folgt erweitert:In the general case, the finally clause expands to:

finally 
{
   (enumerator as IDisposable)?.Dispose();
} 

Wenn es sich bei dem Typ von enumerator jedoch um einen versiegelten Typ handelt und es keine implizite Konvertierung vom Typ von enumerator zu IDisposable gibt, wird die finally-Klausel zu einem leeren Block erweitert:However, if the type of enumerator is a sealed type and there is no implicit conversion from the type of enumerator to IDisposable, the finally clause expands to an empty block:

finally 
{
} 

Wenn es eine implizite Konvertierung vom Typ von enumerator zu IDisposable gibt und enumerator keine NULL-Werte zulässt, wird die finally-Klausel wie folgt erweitert:If there is an implicit conversion from the type of enumerator to IDisposable, and enumerator is a non-nullable value type, the finally clause expands to:

finally 
{
   ((IDisposable)enumerator).Dispose();
} 

Glücklicherweise müssen Sie sich nicht alle diese Details merken.Thankfully, you don't need to remember all these details. Die foreach-Anweisung kümmert sich für Sie um alle diese Nuancen.The foreach statement handles all those nuances for you. Der Compiler generiert den korrekten Code für jedes dieser Konstrukte.The compiler will generate the correct code for any of these constructs.