C# Language
Ergebnis Keyword
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
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
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
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
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 vonIEnumerator.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 einewhile 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
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
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):
- C # -Compiler generiert eine Klasse, die
IEnumerable<BigInteger>
undIEnumerator<BigInteger>
(<Fibonacci>d__0
in ildasm) implementiert. - Diese Klasse implementiert eine Zustandsmaschine. Der Zustand besteht aus der aktuellen Position in der Methode und den Werten der lokalen Variablen.
- Der interessanteste Code befindet sich in der
bool IEnumerator.MoveNext()
Methodebool IEnumerator.MoveNext()
. Grundsätzlich wasMoveNext()
macht:- Stellt den aktuellen Status wieder her. Variablen wie
prev
undcurrent
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 beiyield return
. - Führt Code bis zum nächsten
yield return
oderyield break
/}
. - Für die
yield return
resultierende Wert gespeichert, sodass dieCurrent
Eigenschaft ihn zurückgeben kann.true
wird zurückgegeben. An diesem Punkt wird der aktuelle Status für den nächstenMoveNext
Aufruf erneutMoveNext
. - Für die
yield break
/}
-Methode wird nurfalse
was bedeutet, dass die Iteration durchgeführt wird.
- Stellt den aktuellen Status wieder her. Variablen wie
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;
}