2009-08-25 9 views
27

Ich habe gesehen, dass das Yield-Keyword ziemlich häufig in Stack Overflow und Blogs verwendet wird. Ich verwende LINQ nicht. Kann jemand das Yield-Keyword erklären?Kann jemand das Yield-Keyword entmystifizieren?

Ich weiß, dass ähnliche Fragen bestehen. Aber keiner erklärt wirklich, was es in der einfachen Sprache verwendet.

+12

ziemlich Duplikat: http://stackoverflow.com/questions/39476/what-is-the-yield- Stichwort-verwendet-für-in-c –

+2

okay, das könnte ähnlich sein. Aber die Antworten hier sind anders, viel besser auf diese Frage abgestimmt und besser (w.r.t diese Frage) als die in diesem Beitrag. – Sandbox

+0

Darf ich an dieser Stelle eine Frage stellen? Wann immer ein Typ IEnumerable <> zurückgibt, was bedeutet, dass ich es iterieren kann, oder? Weil es die Methode GetEnumerator() hat? Vielen Dank. – Tarik

Antwort

31

Bei weitem die beste Erklärung dafür (die ich gesehen habe) ist Jon Skeets Buch - und dieses Kapitel ist kostenlos! Kapitel 6, C# in Depth. Ich kann hier nichts hinzufügen, was nicht abgedeckt ist.

Dann kaufen Sie das Buch; Sie werden ein besserer C# -Programmierer dafür sein.


Frage: Warum habe ich hier keine längere Antwort geschrieben (umschrieben von Kommentaren); einfach. Wie Eric Lippert beobachtet (here), das yield Konstrukt (und die Magie, die dahinter geht) ist die einzigen komplexesten Bit des Codes in der C# -Compiler, und zu versuchen und beschreiben es in einer kurzen Antwort hier ist bestenfalls naiv . Es gibt so viele Nuancen zu yield, dass es IMO besser ist, sich auf eine bereits existierende (und voll qualifizierte) Ressource zu beziehen.

Erics Blog hat jetzt 7 Einträge (und das sind nur die jüngsten) diskutieren yield. Ich habe eine riesige Höhe des Respekts für Eric, aber sein Blog ist wahrscheinlich geeigneter als "weitere Informationen" für Menschen, die komfortabel mit dem Thema sind (yield in diesem Fall), wie es in der Regel eine Menge beschreibt Hintergrund Design Überlegungen. Am besten im Rahmen einer vernünftigen Grundlage.

(und ja, Kapitel 6 tut Download, ich überprüft ...)

+1

Also ich bin verwirrt: bin ich Upboting Sie oder Skeet hier? : P – annakata

+10

Illegale Werbung :) – Alex

+2

Es ist egal - wir sind beide für den Tag max; -p –

6

Werfen Sie einen Blick auf die MSDN Dokumentation und das Beispiel. Es ist im Wesentlichen eine einfache Möglichkeit, einen Iterator in C# zu erstellen.

public class List 
{ 
    //using System.Collections; 
    public static IEnumerable Power(int number, int exponent) 
    { 
     int counter = 0; 
     int result = 1; 
     while (counter++ < exponent) 
     { 
      result = result * number; 
      yield return result; 
     } 
    } 

    static void Main() 
    { 
     // Display powers of 2 up to the exponent 8: 
     foreach (int i in Power(2, 8)) 
     { 
      Console.Write("{0} ", i); 
     } 
    } 
} 
+1

Das ist schön. Ziemlich offensichtlich, wofür Yeild benutzt wird. – Tarik

3

yield nicht direkt auf LINQ bezogen, sondern auf iterator blocks. Die verknüpfte MSDN article bietet ausführliche Informationen zu dieser Sprachfunktion. Siehe insbesondere den Abschnitt Using Iterators. Ausführliche Informationen zu Iteratorblöcken finden Sie in Eric Lipperts aktuellem Blog posts zu diesem Feature. Für das allgemeine Konzept, siehe die Wikipedia article auf Iteratoren.

9

Das Schlüsselwort yield ist eine bequeme Möglichkeit, ein IEnumerator zu schreiben. Zum Beispiel:

public static IEnumerator<int> Range(int from, int to) 
{ 
    for (int i = from; i < to; i++) 
    { 
     yield return i; 
    } 
} 

wird durch den C# -Compiler zu etwas verwandelt ähnlich zu:

public static IEnumerator<int> Range(int from, int to) 
{ 
    return new RangeEnumerator(from, to); 
} 

class RangeEnumerator : IEnumerator<int> 
{ 
    private int from, to, current; 

    public RangeEnumerator(int from, int to) 
    { 
     this.from = from; 
     this.to = to; 
     this.current = from; 
    } 

    public bool MoveNext() 
    { 
     this.current++; 
     return this.current < this.to; 
    } 

    public int Current 
    { 
     get 
     { 
      return this.current; 
     } 
    } 
} 
28

Das yield Schlüsselwort mit Methoden verwendet wird, die IEnumerable<T> oder IEnumerator<T> zurückkehren und es macht den Compiler eine Klasse erzeugen, die Arbeitsgeräte die notwendigen Installationen für die Verwendung des Iterators. Z.B.

public IEnumerator<int> SequenceOfOneToThree() { 
    yield return 1; 
    yield return 2; 
    yield return 3; 
} 

die über dem Compiler generiert eine Klasse gegeben, dass IEnumerator<int>, IEnumerable<int> und IDisposable implementiert (es wird tatsächlich auch die nicht-generische Versionen von IEnumerable implementieren und IEnumerator).

Dies ermöglicht, das Verfahren in einer SequenceOfOneToThreeforeach Schleife zu nennen wie diese

foreach(var number in SequenceOfOneToThree) { 
    Console.WriteLine(number); 
} 

Ein Iterator eine Zustandsmaschine ist, so dass jedes Mal yield wird die Position in der Methode aufgerufen wird aufgezeichnet. Wenn der Iterator zum nächsten Element verschoben wird, wird die Methode direkt nach dieser Position fortgesetzt. Die erste Iteration gibt also 1 zurück und markiert diese Position. Der nächste Iterator wird unmittelbar nach einem fortgesetzt und gibt somit 2 und so weiter zurück.

Unnötig zu sagen, dass Sie die Sequenz auf jede beliebige Art und Weise erzeugen können, so dass Sie die Zahlen nicht wie ich hart codieren müssen. Wenn Sie die Schleife unterbrechen möchten, können Sie auch yield break verwenden.

+3

In den 12 Stackoverflow, 2 MSDN und 3 Web-Sites Tabs habe ich geöffnet: das ist die eine zu erklären * warum * ich würde Ausbeute verwenden. Es ist sicherlich ein kniffliges Konzept, das sehr nach voreiliger Optimierung klingt. –

0

Lassen Sie mich all dies hinzufügen. Ertrag ist kein Schlüsselwort. Es funktioniert nur, wenn Sie "yield return" verwenden, außer dass es wie eine normale Variable funktioniert.

Es wird verwendet, um Iterator von einer Funktion zurückzugeben. Sie können weiter darüber suchen. Ich empfehle die Suche nach "Returning Array vs Iterator"

+8

Ertrag ist ein Schlüsselwort von C#. Es ist ein _unreserved kontextuelles Schlüsselwort_, aber es ist immer noch ein Schlüsselwort. –

16

In dem Bemühen zu entmystifizieren Ich werde vermeiden, über Iteratoren zu sprechen, da sie selbst Teil des Mysteriums sein könnten.

Die Renditerückgabe- und Renditebrechungsanweisungen werden am häufigsten verwendet, um eine "verzögerte Bewertung" der Sammlung bereitzustellen.

Dies bedeutet, dass, wenn Sie den Wert einer Methode erhalten, die Rendite zurückgibt, die Sammlung von Dingen, die Sie versuchen zu erhalten, noch nicht zusammen existieren (es ist im Wesentlichen leer). Wenn Sie sie durchlaufen (mit foreach), führt sie die Methode zu diesem Zeitpunkt aus und ruft das nächste Element in der Enumeration ab.

Bestimmte Eigenschaften und Methoden führen dazu, dass die gesamte Enumeration sofort ausgewertet wird (z. B. "Count").

Hier ist ein kurzes Beispiel für den Unterschied zwischen einer Sammlung Rückkehr und Rückkehr Ausbeute:

string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" }; 

public IEnumerable<string> GetYieldEnumerable() 
{ 
    foreach (var name in names) 
     yield return name; 
} 

public IEnumerable<string> GetList() 
{ 
    var list = new List<string>(); 
    foreach (var name in names) 
     list.Add(name); 

    return list; 
} 

// we're going to execute the GetYieldEnumerable() method 
// but the foreach statement inside it isn't going to execute 
var yieldNames = GetNamesEnumerable(); 

// now we're going to execute the GetList() method and 
// the foreach method will execute 
var listNames = GetList(); 

// now we want to look for a specific name in yieldNames. 
// only the first two iterations of the foreach loop in the 
// GetYieldEnumeration() method will need to be called to find it. 
if (yieldNames.Contains("Jim") 
    Console.WriteLine("Found Jim and only had to loop twice!"); 

// now we'll look for a specific name in listNames. 
// the entire names collection was already iterated over 
// so we've already paid the initial cost of looping through that collection. 
// now we're going to have to add two more loops to find it in the listNames 
// collection. 
if (listNames.Contains("Jim")) 
    Console.WriteLine("Found Jim and had to loop 7 times! (5 for names and 2 for listNames)"); 

Dies kann auch verwendet werden, wenn Sie einen Verweis auf die Enumeration erhalten müssen, bevor die Quelldatenwert haben. Zum Beispiel, wenn die Namen Sammlung nicht vollständig war Anfang mit

string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" }; 

public IEnumerable<string> GetYieldEnumerable() 
{ 
    foreach (var name in names) 
     yield return name; 
} 

public IEnumerable<string> GetList() 
{ 
    var list = new List<string>(); 
    foreach (var name in names) 
     list.Add(name); 

    return list; 
} 

var yieldNames = GetNamesEnumerable(); 

var listNames = GetList(); 

// now we'll change the source data by renaming "Jim" to "Jimbo" 
names[1] = "Jimbo"; 

if (yieldNames.Contains("Jimbo") 
    Console.WriteLine("Found Jimbo!"); 

// Because this enumeration was evaluated completely before we changed "Jim" 
// to "Jimbo" it isn't going to be found 
if (listNames.Contains("Jimbo")) 
    // this can't be true 
else 
    Console.WriteLine("Couldn't find Jimbo, because he wasn't there when I was evaluated."); 
+2

hat mir geholfen, es ein wenig mehr zu entmystifizieren =) –

+0

Tolle Erklärung für einen Anfänger auf das Konzept – Aerik

2

Ich kam auf diese zu einem .NET-Nachteile zu überwinden manuell tiefere Kopie Liste zu müssen.

Ich benutze diese:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements) 
{ 
    foreach (SpotPlacement sp in spotPlacements) 
    { 
     yield return (SpotPlacement)sp.Clone(); 
    } 
} 

Und an anderer Stelle:

public object Clone() 
{ 
    OrderItem newOrderItem = new OrderItem(); 
    ... 
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements)); 
    ... 
    return newOrderItem; 
} 

Ich habe versucht, mit oneliner zu kommen, das dies tut, aber es ist nicht möglich, aufgrund Ausbeute nicht in anonymen Arbeits Methodenblöcke.

EDIT:

besser, noch eine allgemeine Liste Kloner verwenden:

class Utility<T> where T : ICloneable 
{ 
    static public IEnumerable<T> CloneList(List<T> tl) 
    { 
     foreach (T t in tl) 
     { 
      yield return (T)t.Clone(); 
     } 
    } 
} 
Verwandte Themen