In der Frage Wie kann ich nur ein Fragment von IList <> verfügbar machen? Hatte eine der Antworten den folgenden Codeausschnitt:
IEnumerable<object> FilteredList()
{
foreach(object item in FullList)
{
if(IsItemInPartialList(item))
yield return item;
}
}
Was macht das Yield-Keyword dort? Ich habe gesehen, dass es an einigen Stellen referenziert wurde, und eine andere Frage, aber ich habe nicht ganz herausgefunden, was es tatsächlich tut. Ich bin es gewohnt, an Ausbeute im Sinne eines Threads zu denken, der einem anderen nachgibt, aber das scheint hier nicht relevant zu sein.
Antworten:
Das
yield
Schlüsselwort macht hier tatsächlich ziemlich viel.Die Funktion gibt ein Objekt zurück, das die
IEnumerable<object>
Schnittstelle implementiert . Wenn eine aufrufende Funktionforeach
über diesem Objekt beginnt , wird die Funktion erneut aufgerufen, bis sie "nachgibt". Dies ist syntaktischer Zucker, der in C # 2.0 eingeführt wurde . In früheren Versionen mussten Sie Ihre eigenenIEnumerable
undIEnumerator
Objekte erstellen, um solche Dinge zu tun.Der einfachste Weg, Code wie diesen zu verstehen, besteht darin, ein Beispiel einzugeben, einige Haltepunkte festzulegen und zu sehen, was passiert. Versuchen Sie, dieses Beispiel durchzugehen:
Wenn Sie durch das Beispiel Schritt, werden Sie den ersten Anruf finden
Integers()
kehrt1
. Der zweite Aufruf kehrt zurück2
und die Leitungyield return 1
wird nicht erneut ausgeführt.Hier ist ein Beispiel aus dem wirklichen Leben:
quelle
yield break;
wenn Sie keine weiteren Artikel zurückgeben möchten.yield
ist kein Schlüsselwort. Wenn es so wäre, könnte ich Yield nicht als Kennung wie inint yield = 500;
'If a calling function starts foreach-ing over this object the function is called again until it "yields"'
. klingt für mich nicht richtig Ich habe immer an das Schlüsselwort c #ield im Zusammenhang mit "Die Ernte bringt eine reichliche Ernte" statt an "Das Auto gibt dem Fußgänger Erträge" gedacht.Wiederholung. Es erstellt eine Zustandsmaschine "unter der Decke", die sich merkt, wo Sie sich in jedem zusätzlichen Zyklus der Funktion befanden, und von dort aus aufnimmt.
quelle
Ertrag hat zwei großartige Verwendungszwecke:
Es ist hilfreich, eine benutzerdefinierte Iteration bereitzustellen, ohne temporäre Sammlungen zu erstellen.
Es hilft, eine zustandsbehaftete Iteration durchzuführen.
Um die beiden obigen Punkte anschaulicher zu erläutern, habe ich ein einfaches Video erstellt, das Sie hier ansehen können
quelle
yield
. @ ShivprasadKoiralas Code-Projektartikel Was nützt C # Yield? der gleichen Erklärung ist auch eine gute Quelleyield
eine "schnelle" Möglichkeit ist, einen benutzerdefinierten IEnumerator zu erstellen (statt dass eine Klasse die IEnumerator-Schnittstelle implementiert).Kürzlich hat Raymond Chen auch eine interessante Artikelserie zum Ertragsschlüsselwort veröffentlicht.
Es wird zwar nominell zur einfachen Implementierung eines Iteratormusters verwendet, kann aber zu einer Zustandsmaschine verallgemeinert werden. Es macht keinen Sinn, Raymond zu zitieren, der letzte Teil verweist auch auf andere Verwendungen (aber das Beispiel in Entins Blog ist besonders gut und zeigt, wie man asynchronen sicheren Code schreibt).
quelle
Auf den ersten Blick ist Yield Return ein .NET- Zucker, um eine IEnumerable zurückzugeben .
Ohne Ertrag werden alle Elemente der Sammlung auf einmal erstellt:
Gleicher Code mit Ertrag, der Artikel für Artikel zurückgibt:
Der Vorteil der Verwendung von Yield besteht darin, dass der Rest der Elemente nicht erstellt wird, wenn die Funktion, die Ihre Daten verbraucht, lediglich das erste Element der Sammlung benötigt.
Der Ertragsoperator ermöglicht die Erstellung von Artikeln nach Bedarf. Das ist ein guter Grund, es zu benutzen.
quelle
yield return
wird mit Enumeratoren verwendet. Bei jedem Aufruf der Yield-Anweisung wird die Kontrolle an den Anrufer zurückgegeben, es wird jedoch sichergestellt, dass der Status des Angerufenen beibehalten wird. Wenn der Aufrufer das nächste Element auflistet, setzt er daher die Ausführung in der callee-Methode ab Anweisung unmittelbar nach deryield
Anweisung fort.Versuchen wir dies anhand eines Beispiels zu verstehen. In diesem Beispiel habe ich entsprechend jeder Zeile die Reihenfolge erwähnt, in der die Ausführung erfolgt.
Außerdem wird der Status für jede Aufzählung beibehalten. Angenommen, ich habe einen weiteren Aufruf der
Fibs()
Methode, dann wird der Status dafür zurückgesetzt.quelle
Intuitiv gibt das Schlüsselwort einen Wert aus der Funktion zurück, ohne ihn zu verlassen, dh in Ihrem Codebeispiel gibt es den aktuellen
item
Wert zurück und setzt dann die Schleife fort. Formal wird es vom Compiler verwendet, um Code für einen Iterator zu generieren . Iteratoren sind Funktionen, dieIEnumerable
Objekte zurückgeben. Das MSDN enthält mehrere Artikel darüber.quelle
Eine Listen- oder Array-Implementierung lädt alle Elemente sofort, während die Yield-Implementierung eine verzögerte Ausführungslösung bietet.
In der Praxis ist es häufig wünschenswert, den Mindestarbeitsaufwand nach Bedarf auszuführen, um den Ressourcenverbrauch einer Anwendung zu reduzieren.
Beispielsweise haben wir möglicherweise eine Anwendung, die Millionen von Datensätzen aus einer Datenbank verarbeitet. Die folgenden Vorteile können erzielt werden, wenn IEnumerable in einem Pull-basierten Modell mit verzögerter Ausführung verwendet wird:
Hier ist ein Vergleich zwischen dem ersten Erstellen einer Sammlung, z. B. einer Liste, und dem Verwenden von Yield.
Listenbeispiel
Konsolenausgabe
ContactListStore: Erstellen eines Kontakts 1
ContactListStore: Erstellen eines Kontakts 2
ContactListStore: Erstellen eines Kontakts 3
Bereit zum Durchlaufen der Sammlung.
Hinweis: Die gesamte Sammlung wurde in den Speicher geladen, ohne nach einem einzelnen Element in der Liste zu fragen
Ertragsbeispiel
Konsolenausgabe
Bereit zum Durchlaufen der Sammlung.
Hinweis: Die Sammlung wurde überhaupt nicht ausgeführt. Dies ist auf die "verzögerte Ausführung" von IEnumerable zurückzuführen. Das Erstellen eines Elements erfolgt nur, wenn es wirklich benötigt wird.
Rufen wir die Sammlung erneut auf und beobachten das Verhalten, wenn wir den ersten Kontakt in der Sammlung abrufen.
Konsolenausgabe
Bereit zum Durchlaufen der Sammlung
ContactYieldStore: Erstellen von Kontakt 1
Hallo Bob
Nett! Nur der erste Kontakt wurde erstellt, als der Kunde den Artikel aus der Sammlung "zog".
quelle
Hier ist ein einfacher Weg, um das Konzept zu verstehen: Die Grundidee ist, wenn Sie eine Sammlung möchten, für die Sie "
foreach
" verwenden können, das Sammeln der Elemente in der Sammlung jedoch aus irgendeinem Grund teuer ist (z. B. das Abfragen aus einer Datenbank). UND Sie benötigen häufig nicht die gesamte Sammlung. Anschließend erstellen Sie eine Funktion, mit der die Sammlung einzeln erstellt und an den Verbraucher zurückgegeben wird (der den Sammlungsaufwand dann vorzeitig beenden kann).Stellen Sie sich das so vor: Sie gehen zur Fleischtheke und möchten ein Pfund geschnittenen Schinken kaufen. Der Metzger nimmt einen 10-Pfund-Schinken nach hinten, legt ihn auf die Schneidemaschine, schneidet das Ganze in Scheiben, bringt dann den Scheibenhaufen zu Ihnen zurück und misst ein Pfund davon ab. (Alter Weg). Mit
yield
bringt der Metzger die Schneidemaschine zur Theke und beginnt, jede Scheibe auf der Waage zu schneiden und "nachzugeben", bis sie 1 Pfund misst. Dann wickelt er sie für Sie ein und Sie sind fertig. Der alte Weg mag für den Metzger besser sein (lässt ihn seine Maschinen so organisieren, wie er möchte), aber der neue Weg ist in den meisten Fällen für den Verbraucher deutlich effizienter.quelle
Mit dem
yield
Schlüsselwort können Sie einIEnumerable<T>
Formular in einem Iteratorblock erstellen . Dieser Iteratorblock unterstützt die verzögerte Ausführung. Wenn Sie mit dem Konzept nicht vertraut sind, kann es fast magisch erscheinen. Letztendlich ist es jedoch nur Code, der ohne seltsame Tricks ausgeführt wird.Ein Iteratorblock kann als syntaktischer Zucker beschrieben werden, bei dem der Compiler eine Zustandsmaschine generiert, die verfolgt, wie weit die Aufzählung der Aufzählung fortgeschritten ist. Um eine Aufzählung aufzulisten, verwenden Sie häufig eine
foreach
Schleife. Eineforeach
Schleife ist jedoch auch syntaktischer Zucker. Sie sind also zwei Abstraktionen, die aus dem realen Code entfernt wurden, weshalb es anfangs schwierig sein kann zu verstehen, wie alles zusammenarbeitet.Angenommen, Sie haben einen sehr einfachen Iteratorblock:
Echte Iteratorblöcke haben häufig Bedingungen und Schleifen, aber wenn Sie die Bedingungen überprüfen und die Schleifen abrollen, werden sie immer noch als
yield
Anweisungen ausgegeben, die mit anderem Code verschachtelt sind.Um den Iteratorblock aufzulisten, wird eine
foreach
Schleife verwendet:Hier ist die Ausgabe (keine Überraschungen hier):
Wie oben angegeben
foreach
ist syntaktischer Zucker:Um dies zu entwirren, habe ich ein Sequenzdiagramm mit den entfernten Abstraktionen erstellt:
Die vom Compiler generierte Zustandsmaschine implementiert auch den Enumerator, aber um das Diagramm klarer zu machen, habe ich sie als separate Instanzen gezeigt. (Wenn die Zustandsmaschine von einem anderen Thread aufgezählt wird, erhalten Sie tatsächlich separate Instanzen, aber dieses Detail ist hier nicht wichtig.)
Jedes Mal, wenn Sie Ihren Iteratorblock aufrufen, wird eine neue Instanz der Zustandsmaschine erstellt. Ihr Code im Iteratorblock wird jedoch erst ausgeführt, wenn er
enumerator.MoveNext()
zum ersten Mal ausgeführt wird. So funktioniert die verzögerte Ausführung. Hier ist ein (ziemlich dummes) Beispiel:Zu diesem Zeitpunkt wurde der Iterator noch nicht ausgeführt. Die
Where
Klausel erstellt eine neueIEnumerable<T>
, die dieIEnumerable<T>
zurückgegebenen vonIteratorBlock
umschließt, aber diese Aufzählung muss noch aufgelistet werden. Dies geschieht, wenn Sie eineforeach
Schleife ausführen :Wenn Sie die Aufzählung zweimal auflisten, wird jedes Mal eine neue Instanz der Zustandsmaschine erstellt, und Ihr Iteratorblock führt denselben Code zweimal aus.
Beachten Sie, dass LINQ Methoden wie
ToList()
,ToArray()
,First()
,Count()
wird usw. , eine Verwendungforeach
Schleife die enumerable aufzuzählen. Zum BeispielToList()
werden alle Elemente der Aufzählung aufgelistet und in einer Liste gespeichert. Sie können jetzt auf die Liste zugreifen, um alle Elemente der Aufzählung abzurufen, ohne dass der Iteratorblock erneut ausgeführt wird. Es gibt einen Kompromiss zwischen der Verwendung der CPU, um die Elemente der Aufzählung mehrfach zu erzeugen, und dem Speicher, um die Elemente der Aufzählung zu speichern, um mehrmals auf sie zuzugreifen, wenn Methoden wie verwendet werdenToList()
.quelle
Wenn ich das richtig verstehe, würde ich dies aus der Perspektive der Funktion, die IEnumerable mit Ertrag implementiert, folgendermaßen formulieren.
quelle
Das C # Yield-Schlüsselwort ermöglicht, um es einfach auszudrücken, viele Aufrufe eines Code-Körpers, der als Iterator bezeichnet wird, der weiß, wie er zurückkehrt, bevor er fertig ist, und bei einem erneuten Aufruf dort fortgesetzt wird, wo er aufgehört hat - dh einem Iterator hilft Werden Sie für jedes Element in einer Sequenz, die der Iterator in aufeinanderfolgenden Aufrufen zurückgibt, transparent.
In JavaScript wird das gleiche Konzept als Generatoren bezeichnet.
quelle
Es ist eine sehr einfache und einfache Möglichkeit, eine Aufzählung für Ihr Objekt zu erstellen. Der Compiler erstellt eine Klasse, die Ihre Methode umschließt und in diesem Fall IEnumerable <Objekt> implementiert. Ohne das Schlüsselwort yield müssten Sie ein Objekt erstellen, das IEnumerable <Objekt> implementiert.
quelle
Es produziert unzählige Sequenzen. Tatsächlich wird eine lokale IEnumerable-Sequenz erstellt und als Methodenergebnis zurückgegeben
quelle
Dieser Link hat ein einfaches Beispiel
Noch einfachere Beispiele finden Sie hier
Beachten Sie, dass die Rendite von der Methode nicht zurückgegeben wird. Sie können sogar eine
WriteLine
nach dem setzenyield return
Das Obige ergibt eine IE-Zahl von 4 Zoll 4,4,4,4
Hier mit einem
WriteLine
. Fügt der Liste 4 hinzu, druckt abc, fügt dann 4 zur Liste hinzu, vervollständigt dann die Methode und kehrt so wirklich von der Methode zurück (sobald die Methode abgeschlossen ist, wie es bei einer Prozedur ohne Rückgabe der Fall wäre). Dies hätte jedoch einen Wert, eineIEnumerable
Liste vonint
s, die nach Abschluss zurückgegeben wird.Beachten Sie auch, dass das, was Sie zurückgeben, bei Verwendung vonield nicht vom gleichen Typ wie die Funktion ist. Es ist vom Typ eines Elements in der
IEnumerable
Liste.Sie verwenden Yield mit dem Rückgabetyp der Methode als
IEnumerable
. Wenn der Rückgabetyp der Methodeint
oder istList<int>
und Sie verwendenyield
, wird sie nicht kompiliert. Sie können denIEnumerable
Methodenrückgabetyp ohne Ertrag verwenden, aber es scheint, dass Sie den Ertrag ohneIEnumerable
Methodenrückgabetyp nicht verwenden können .Und um es auszuführen, müssen Sie es auf besondere Weise aufrufen.
quelle
public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { yield return t; }
undpublic static IEnumerable<TResult> testYieldc<TResult>(TResult t) { return new List<TResult>(); }
yield return
(abgesehen von der einfachen Sache, die ich erwähnt habe) und es nicht viel benutzt habe und nicht viel über seine Verwendung weiß, denke ich nicht, dass dies die akzeptierte sein sollte.Ein wichtiger Punkt beim Yield-Schlüsselwort ist Lazy Execution . Mit Lazy Execution meine ich nun, dass es bei Bedarf ausgeführt wird. Ein besserer Weg, es auszudrücken, ist ein Beispiel zu geben
Beispiel: Ertrag nicht verwenden, dh keine verzögerte Ausführung.
Beispiel: Verwenden von Yield, dh Lazy Execution.
Nun, wenn ich beide Methoden aufrufe.
Sie werden feststellen, dass listItems 5 Elemente enthält (bewegen Sie die Maus beim Debuggen über listItems). Während YieldItems nur einen Verweis auf die Methode und nicht auf die Items haben. Das bedeutet, dass der Prozess zum Abrufen von Elementen innerhalb der Methode nicht ausgeführt wurde. Eine sehr effiziente Möglichkeit, Daten nur bei Bedarf abzurufen. Die tatsächliche Umsetzung der Rendite kann in ORM wie Entity Framework und NHibernate usw. gesehen werden.
quelle
Es wird versucht, Ruby Goodness einzubringen :)
Konzept: Dies ist ein Beispiel für Ruby-Code, der jedes Element des Arrays ausdruckt
Das Array ist jede Methode Implementierung Ausbeuten an den Aufrufer Kontrolle über (die ‚setzt x‘) mit jedem Element des Arrays ordentlich als x dargestellt. Der Anrufer kann dann mit x alles tun, was er braucht.
Allerdings .Net geht nicht den ganzen Weg hier .. C # mit IEnumerable zu haben gekoppelt Ausbeute scheint, in einer Art und Weise zwingt Sie eine foreach - Schleife in dem Anrufer zu schreiben , wie in Mendelt Antwort gesehen. Etwas weniger elegant.
quelle
yield
ist mit gekoppeltIEnumerable
, und C # fehlt das Ruby-Konzept eines "Blocks". Aber C # hat Lambdas, die die Implementierung einerForEach
Methode ermöglichen könnten , ähnlich wie bei Rubyeach
. Dies bedeutet jedoch nicht, dass dies eine gute Idee wäre.