Suche…


Einführung

Wenn Sie das Ertragsschlüsselwort in einer Anweisung verwenden, geben Sie an, dass die Methode, der Operator oder der Accessor, in dem es angezeigt wird, ein Iterator sind. Durch die Verwendung von Yield zur Definition eines Iterators wird keine explizite zusätzliche Klasse benötigt (die Klasse, die den Status für eine Aufzählung enthält), wenn Sie das IEnumerable- und das IEnumerator-Muster für einen benutzerdefinierten Auflistungstyp implementieren.

Syntax

  • Rendite Rendite [TYP]
  • Rendite brechen

Bemerkungen

Setzt man die yield Schlüsselwort in einem Verfahren mit dem Rückgabetyp IEnumerable , IEnumerable<T> , IEnumerator oder IEnumerator<T> weist den Compiler eine Implementierung des Rückgabetypen (zu generieren IEnumerable oder IEnumerator ) , die, wenn geschlungen über läuft die Methode bis zu jeder "Ausbeute", um jedes Ergebnis zu erhalten.

Die yield Schlüsselwort ist nützlich , wenn Sie „das nächste“ Element einer theoretisch unbegrenzten Sequenz zurückkehren wollen, so die gesamte Sequenz Berechnung vorher unmöglich wären, oder wenn die vollständige Sequenz von Werten vor der Rückkehr in der Berechnung für den Benutzer zu einer unerwünschten Pause führen würden .

yield break kann auch verwendet werden, um die Sequenz jederzeit zu beenden.

Da für das yield Schlüsselwort ein Iterator-Schnittstellentyp als Rückgabetyp erforderlich ist, z. B. IEnumerable<T> , können Sie dies nicht in einer asynchronen Methode verwenden, da dies ein Task<IEnumerable<T>> -Objekt Task<IEnumerable<T>> .

Lesen Sie weiter

Einfache Verwendung

Mit dem Schlüsselwort yield wird eine Funktion definiert, die einen IEnumerable oder IEnumerator (sowie die abgeleiteten generischen Varianten) IEnumerator deren Werte IEnumerator generiert werden, wenn ein Aufrufer die zurückgegebene Auflistung IEnumerator . Lesen Sie mehr über den Zweck in den Anmerkungen .

Das folgende Beispiel enthält eine Anweisung für die Rendite, die sich in einer for Schleife befindet.

public static IEnumerable<int> Count(int start, int count)
{
    for (int i = 0; i <= count; i++)
    {
        yield return start + i;
    }
}

Dann kannst du es nennen:

foreach (int value in Count(start: 4, count: 10))
{
    Console.WriteLine(value);
}

Konsolenausgabe

4
5
6
...
14

Live-Demo zu .NET-Geige

Bei jeder Iteration des foreach Anweisungstextes wird ein Aufruf der Count Iterator-Funktion erstellt. Jeder Aufruf der Iterator-Funktion geht zur nächsten Ausführung der yield return Anweisung über, die während der nächsten Iteration der for Schleife auftritt.

Weitere sachdienliche Verwendung

public IEnumerable<User> SelectUsers()
{
    // Execute an SQL query on a database.
    using (IDataReader reader = this.Database.ExecuteReader(CommandType.Text, "SELECT Id, Name FROM Users"))
    {
        while (reader.Read())
        {
            int id = reader.GetInt32(0);
            string name = reader.GetString(1);
            yield return new User(id, name);
        }
    }
}

Es gibt auch andere Möglichkeiten, einen immer IEnumerable<User> aus einer SQL - Datenbank, natürlich - dies zeigt nur , dass Sie verwenden können , yield etwas zu verwandeln , die „Folge von Elementen“ Semantik in eine hat IEnumerable<T> , dass jemand kann über iterieren .

Vorzeitige Beendigung

Sie können die Funktionalität vorhandener yield Methoden erweitern, indem Sie einen oder mehrere Werte oder Elemente übergeben, die möglicherweise eine Beendigungsbedingung innerhalb der Funktion definieren, indem Sie einen yield break aufrufen, um die Ausführung der inneren Schleife zu stoppen.

public static IEnumerable<int> CountUntilAny(int start, HashSet<int> earlyTerminationSet)
{
    int curr = start;

    while (true)
    {
        if (earlyTerminationSet.Contains(curr))
        {
            // we've hit one of the ending values
            yield break;
        }

        yield return curr;

        if (curr == Int32.MaxValue)
        {
            // don't overflow if we get all the way to the end; just stop
            yield break;
        }

        curr++;
    }
}

Das oben genannte Verfahren von einer gegebenen iterieren würde start , bis einem der Werte innerhalb des earlyTerminationSet angetroffen wurde.

// Iterate from a starting point until you encounter any elements defined as 
// terminating elements
var terminatingElements = new HashSet<int>{ 7, 9, 11 };
// This will iterate from 1 until one of the terminating elements is encountered (7)
foreach(var x in CountUntilAny(1,terminatingElements))
{
    // This will write out the results from 1 until 7 (which will trigger terminating)
    Console.WriteLine(x);
}

Ausgabe:

1
2
3
4
5
6

Live-Demo zu .NET-Geige

Argumente richtig prüfen

Eine Iterator-Methode wird erst ausgeführt, wenn der Rückgabewert aufgezählt ist. Es ist daher vorteilhaft, Vorbedingungen außerhalb des Iterators zu behaupten.

public static IEnumerable<int> Count(int start, int count)
{
    // The exception will throw when the method is called, not when the result is iterated
    if (count < 0)
        throw new ArgumentOutOfRangeException(nameof(count));

    return CountCore(start, count);
}

private static IEnumerable<int> CountCore(int start, int count)
{
    // If the exception was thrown here it would be raised during the first MoveNext()
    // call on the IEnumerator, potentially at a point in the code far away from where
    // an incorrect value was passed.
    for (int i = 0; i < count; i++)
    {
        yield return start + i;
    }
}

Calling Side Code (Verwendung):

// Get the count
var count = Count(1,10);
// Iterate the results
foreach(var x in count)
{
    Console.WriteLine(x);
}

Ausgabe:

1
2
3
4
5
6
7
8
9
10

Live-Demo zu .NET-Geige

Wenn eine Methode yield , um ein Aufzählungszeichen zu generieren, erstellt der Compiler eine Zustandsmaschine, die bei Iteration Code bis zu einem yield . Es gibt dann das Ergebnis zurück und speichert seinen Status.

Dies bedeutet, dass Sie beim ersten Aufruf der Methode (da die Statusmaschine erstellt wird) nicht über ungültige Argumente informiert werden (das Übergeben von null usw.), nur wenn Sie versuchen, auf das erste Element zuzugreifen (da nur dann der Code innerhalb der Methode wird von der Zustandsmaschine ausgeführt). Durch das Einbetten einer normalen Methode, die zuerst Argumente prüft, können Sie sie beim Aufruf der Methode überprüfen. Dies ist ein Beispiel für ein schnelles Versagen.

Bei Verwendung von C # 7+ kann die CountCore Funktion bequem als lokale Funktion in der Count Funktion ausgeblendet werden. Siehe Beispiel hier .

Gibt ein anderes Enumerable innerhalb einer Methode zurück, die Enumerable zurückgibt

public IEnumerable<int> F1()
{
    for (int i = 0; i < 3; i++)
        yield return i;

    //return F2(); // Compile Error!!
    foreach (var element in F2())
        yield return element;
}

public int[] F2()
{
    return new[] { 3, 4, 5 };
}

Faule Bewertung

Erst wenn die foreach Anweisung zum nächsten Element wechselt, wird der Iteratorblock bis zur nächsten yield ausgewertet.

Betrachten Sie das folgende Beispiel:

private IEnumerable<int> Integers()
{
    var i = 0;
    while(true)
    {
        Console.WriteLine("Inside iterator: " + i);
        yield return i;
        i++;
    }
}

private void PrintNumbers()
{
    var numbers = Integers().Take(3);
    Console.WriteLine("Starting iteration");

    foreach(var number in numbers)
    {
        Console.WriteLine("Inside foreach: " + number);
    }
}

Dies wird ausgegeben:

Iteration starten
Innerer Iterator: 0
Innerhalb von foreach: 0
Innerer Iterator: 1
Innenbereich: 1
Innerer Iterator: 2
Innenbereich: 2

Demo anzeigen

Als Konsequenz:

  • "Iteration starten" wird zuerst gedruckt, obwohl die Iteratormethode vor dem Drucken der Zeile aufgerufen wurde, da die Zeile Integers().Take(3); startet die Iteration nicht wirklich (kein Aufruf von IEnumerator.MoveNext() wurde durchgeführt)
  • Die Zeilen, die auf die Konsole gedruckt werden, wechseln zwischen der Zeile innerhalb der Iterator-Methode und der Zeile innerhalb des foreach , anstatt alle Zeilen innerhalb der Iterator-Methode, die zuerst bewertet werden
  • Dieses Programm wird aufgrund der .Take() -Methode beendet, obwohl die Iterator-Methode eine while true der sie niemals ausbricht.

Versuchen Sie es ... endlich

Wenn eine Iterator-Methode innerhalb eines try...finally IEnumerator eine Rendite hat, führt der zurückgegebene IEnumerator die finally Anweisung aus, wenn Dispose für ihn aufgerufen wird, solange sich der aktuelle Bewertungspunkt im try Block befindet.

Angesichts der Funktion:

private IEnumerable<int> Numbers()
{
    yield return 1;
    try
    {
        yield return 2;
        yield return 3;
    }
    finally
    {
        Console.WriteLine("Finally executed");
    }
}

Wenn Sie anrufen:

private void DisposeOutsideTry()
{
    var enumerator = Numbers().GetEnumerator();

    enumerator.MoveNext();
    Console.WriteLine(enumerator.Current);
    enumerator.Dispose();
}

Dann druckt es:

1

Demo anzeigen

Wenn Sie anrufen:

private void DisposeInsideTry()
{
    var enumerator = Numbers().GetEnumerator();

    enumerator.MoveNext();
    Console.WriteLine(enumerator.Current);
    enumerator.MoveNext();
    Console.WriteLine(enumerator.Current);
    enumerator.Dispose();
}

Dann druckt es:

1
2
Endlich ausgeführt

Demo anzeigen

Verwenden von Yield, um einen IEnumerator zu erstellen bei der Implementierung von IEnumerable

Die IEnumerable<T> -Schnittstelle hat eine einzige Methode, GetEnumerator() , die einen IEnumerator<T> .

Während die yield Schlüsselwort verwendet werden kann , um direkt eine erstellen IEnumerable<T> , kann es auch auf genau die gleiche Art und Weise verwendet werden , um eine erstellen IEnumerator<T> . Das einzige, was sich ändert, ist der Rückgabetyp der Methode.

Dies kann nützlich sein, wenn wir eine eigene Klasse erstellen möchten, die IEnumerable<T> implementiert:

public class PrintingEnumerable<T> : IEnumerable<T>
{
    private IEnumerable<T> _wrapped;

    public PrintingEnumerable(IEnumerable<T> wrapped)
    {
        _wrapped = wrapped;
    }

    // This method returns an IEnumerator<T>, rather than an IEnumerable<T>
    // But the yield syntax and usage is identical.
    public IEnumerator<T> GetEnumerator()
    {
        foreach(var item in _wrapped)
        {
            Console.WriteLine("Yielding: " + item);
            yield return item;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

(Beachten Sie, dass dieses spezielle Beispiel nur zur Veranschaulichung dient und mit einer einzigen Iterator-Methode, die ein IEnumerable<T> sauberer implementiert werden könnte.)

Eifrige Bewertung

Das yield ermöglicht eine faule Bewertung der Sammlung. Das zwangsweise Laden der gesamten Sammlung in den Speicher wird als eifrige Auswertung bezeichnet .

Der folgende Code zeigt dies:

IEnumerable<int> myMethod()
{
    for(int i=0; i <= 8675309; i++)
    {
        yield return i;
    }
}
...
// define the iterator
var it = myMethod.Take(3);
// force its immediate evaluation
// list will contain 0, 1, 2
var list = it.ToList();

Beim Aufruf von ToList , ToDictionary oder ToArray wird die sofortige Bewertung der Aufzählung ToList , und alle Elemente werden in eine Auflistung abgerufen.

Faules Bewertungsbeispiel: Fibonacci-Zahlen

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics; // also add reference to System.Numberics

namespace ConsoleApplication33
{
    class Program
    {
        private static IEnumerable<BigInteger> Fibonacci()
        {
            BigInteger prev = 0;
            BigInteger current = 1;
            while (true)
            {
                yield return current;
                var next = prev + current;
                prev = current;
                current = next;
            }
        }

        static void Main()
        {
            // print Fibonacci numbers from 10001 to 10010
            var numbers = Fibonacci().Skip(10000).Take(10).ToArray();
            Console.WriteLine(string.Join(Environment.NewLine, numbers));
        }
    }
}

So funktioniert es unter der Haube (ich empfehle die resultierende .exe-Datei in IL Disaambler-Tool zu dekompilieren):

  1. C # -Compiler generiert eine Klasse, die IEnumerable<BigInteger> und IEnumerator<BigInteger> ( <Fibonacci>d__0 in ildasm) implementiert.
  2. Diese Klasse implementiert eine Zustandsmaschine. Der Zustand besteht aus der aktuellen Position in der Methode und den Werten der lokalen Variablen.
  3. Der interessanteste Code befindet sich in der bool IEnumerator.MoveNext() Methode bool IEnumerator.MoveNext() . Grundsätzlich was MoveNext() macht:
    • Stellt den aktuellen Status wieder her. Variablen wie prev und current werden zu Feldern in unserer Klasse ( <current>5__2 und <prev>5__1 in ildasm). In unserer Methode haben wir zwei Positionen ( <>1__state ): zuerst an der öffnenden geschweiften Klammer, an zweiter Stelle bei yield return .
    • Führt Code bis zum nächsten yield return oder yield break / } .
    • Für die yield return resultierende Wert gespeichert, sodass die Current Eigenschaft ihn zurückgeben kann. true wird zurückgegeben. An diesem Punkt wird der aktuelle Status für den nächsten MoveNext Aufruf erneut MoveNext .
    • Für die yield break / } -Methode wird nur false was bedeutet, dass die Iteration durchgeführt wird.

Beachten Sie auch, dass die 10001. Nummer 468 Byte lang ist. State Machine speichert nur current und prev Variablen als Felder. Wenn wir jedoch alle Zahlen in der Reihenfolge von der ersten bis zur 10000sten speichern möchten, beträgt die Speichergröße mehr als 4 Megabyte. So kann eine faule Auswertung, wenn sie richtig verwendet wird, in einigen Fällen den Speicherbedarf reduzieren.

Der Unterschied zwischen Break und Yield Break

Die Verwendung von yield break im Gegensatz zu break möglicherweise nicht so offensichtlich, wie man vielleicht denkt. Es gibt viele schlechte Beispiele im Internet, bei denen die Verwendung der beiden austauschbar ist und den Unterschied nicht wirklich veranschaulicht.

Der verwirrende Teil ist, dass beide Schlüsselwörter (oder Schlüsselphrasen) nur innerhalb von Schleifen Sinn machen ( foreach , while ...). Wann also eines für das andere?

Es ist wichtig zu wissen, dass die Methode in einen Iterator umgewandelt wird , sobald Sie das yield Schlüsselwort in einer Methode verwenden. Der einzige Zweck einer solchen Methode besteht darin, eine endliche oder unendliche Sammlung und Ausbeute (Ausgabe) ihrer Elemente zu durchlaufen. Sobald der Zweck erfüllt ist, gibt es keinen Grund, die Ausführung der Methode fortzusetzen. Manchmal passiert das natürlich mit der letzten schließenden Klammer der Methode } . Aber manchmal möchten Sie die Methode vorzeitig beenden. In einer normalen (nicht iterierenden) Methode würden Sie das Schlüsselwort return . Aber man kann nicht verwenden return in einem Iterator, haben Sie verwenden yield break . Mit anderen Worten, yield break für einen Iterator ist die gleiche wie return für eine Standardmethode. Während die break Anweisung nur die nächste Schleife beendet.

Sehen wir uns einige Beispiele an:

    /// <summary>
    /// Yields numbers from 0 to 9
    /// </summary>
    /// <returns>{0,1,2,3,4,5,6,7,8,9}</returns>
    public static IEnumerable<int> YieldBreak()
    {
        for (int i = 0; ; i++)
        {
            if (i < 10)
            {
                // Yields a number
                yield return i;
            }
            else
            {
                // Indicates that the iteration has ended, everything 
                // from this line on will be ignored
                yield break;
            }
        }
        yield return 10; // This will never get executed
    }
    /// <summary>
    /// Yields numbers from 0 to 10
    /// </summary>
    /// <returns>{0,1,2,3,4,5,6,7,8,9,10}</returns>
    public static IEnumerable<int> Break()
    {
        for (int i = 0; ; i++)
        {
            if (i < 10)
            {
                // Yields a number
                yield return i;
            }
            else
            {
                // Terminates just the loop
                break;
            }
        }
        // Execution continues
        yield return 10;
    }


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow