2008-12-21 19 views
8

immer noch versuchen zu finden, wo ich das "Yield" Keyword in einer realen Situation verwenden würde.Yield Keyword Value Added?

Ich sehe diesen Thread zu dem Thema

What is the yield keyword used for in C#?

aber in der akzeptierte Antwort, sie haben dies als Beispiel, wo jemand um Integers iteriert()

public IEnumerable<int> Integers() 
{ 
yield return 1; 
yield return 2; 
yield return 4; 
yield return 8; 
yield return 16; 
yield return 16777216; 
} 

aber warum nicht verwenden Sie einfach

list<int> 

hier statt. einfacher scheint ..

+0

möglich Duplikat (http://stackoverflow.com/questions/17125/what-are-real-life-Anwendungen-of-Yield) – nawfal

+0

Eine Antwort finden Sie hier: http://StackOverflow.com/Questions/14057788/Why-use-the-Yield-Keyword-when -i-can-just-use-an-normal-ienumerable – hdoghmen

Antwort

23

Wenn Sie eine Liste erstellen und zurückgeben (sagen, es hat 1 Million Elemente), das ist ein großer Teil des Speichers, und auch von Arbeit, um es zu schaffen.

Manchmal möchte der Anrufer vielleicht nur wissen, was das erste Element ist. Oder sie möchten sie in eine Datei schreiben, wenn sie sie erhalten, anstatt die gesamte Liste im Speicher zu erstellen und sie dann in eine Datei zu schreiben.

Aus diesem Grund ist es sinnvoller, Rendite zu verwenden. Es sieht nicht so anders aus, als die gesamte Liste zu erstellen und zurückzugeben, aber es ist sehr unterschiedlich, da die gesamte Liste nicht im Speicher erstellt werden muss, bevor der Aufrufer das erste Element darauf betrachten kann.

Wenn der Anrufer sagt:

foreach (int i in Integers()) 
{ 
    // do something with i 
} 

Jedes Mal, wenn die Schleife einen neuen i erfordert, läuft es ein bisschen mehr von dem Code in ganzen Zahlen(). Der Code in dieser Funktion ist "pausiert", wenn er eine yield return Anweisung trifft.

+1

Ich hatte mit Problemen den Ertrag zu verstehen. Aber deine Antwort war nett! Ich denke, die Verwendung von Ertrag entspricht mehr oder weniger dem Unterschied zwischen DataReader und DataSets. Mit DataSets haben wir alle Daten, dann arbeiten wir mit DataReadern und Sie können mit den Daten arbeiten, während sie von der Quelle kommen. :-) –

0

Sie könnten durch verschiedene Sammlungen iterieren wollen:

public IEnumerable<ICustomer> Customers() 
{ 
     foreach(ICustomer customer in m_maleCustomers) 
     { 
      yield return customer; 
     } 

     foreach(ICustomer customer in m_femaleCustomers) 
     { 
      yield return customer; 
     } 

     // or add some constraints... 
     foreach(ICustomer customer in m_customers) 
     { 
      if(customer.Age < 16) 
      { 
       yield return customer; 
      } 
     } 

     // Or....    
     if(Date.Today == 1) 
     { 
      yield return m_superCustomer; 
     } 

} 
+1

Wenn Sie interessiert sind (und nicht von Linq), können Sie th schreiben bei der ganzen Sache als: zurück m_maleCustomers.Concat (m_femaleCustomers) .Concat (m_customers.Where (c => c.Age <16)). Concat (Enumerable.Repeat (m_superCustomer, 1) .Where (Date.Today == 1) ; –

4

Sie können yield verwenden, um einen Iterator zu erstellen. Das könnte eine träge ausgewertete Serie sein (z. B. Lesen von Zeilen aus einer Datei oder einer Datenbank, ohne alles auf einmal zu lesen, was zu viel im Speicher sein könnte), oder könnte über bestehende Daten wie List<T> iterieren.

C# in Depth hat ein freies Kapitel (6) alles über Iterator Blöcke.

Ich auch blogged vor kurzem über die Verwendung yield für intelligente Brute-Force-Algorithmen.

Ein Beispiel für die Faulen Datei Leser:

static IEnumerable<string> ReadLines(string path) { 
     using (StreamReader reader = File.OpenText(path)) { 
      string line; 
      while ((line = reader.ReadLine()) != null) { 
       yield return line; 
      } 
     } 
    } 

Dies ist völlig "faul"; nichts wird gelesen, bis Sie mit dem Aufzählen beginnen, und nur eine einzelne Zeile wird jemals im Speicher gehalten.

Beachten Sie, dass LINQ-to-Objects umfangreiche Verwendung von Iterator-Blöcken (yield) macht.im Wesentlichen Zum Beispiel ist die Where Erweiterung:

static IEnumerable<T> Where<T>(this IEnumerable<T> data, Func<T, bool> predicate) { 
     foreach (T item in data) { 
      if (predicate(item)) yield return item; 
     } 
    } 

Und wieder voll faul - so dass Sie verketten mehrere Operationen ohne alles zwingt in den Speicher geladen werden.

+0

Schöner Artikel über die faule Brute-Force, wie wäre es, wenn Sie eine Liste mit einem einzelnen Objekt erwarten, würden Sie eine Single() verwenden, um das sicherzustellen? ist es eine gute Praxis? – CloudyMarble

+0

@CloudyMarble sicher, das ist ein vollkommen vernünftiger Weg, um sicherzustellen, dass es validiert wird.Beachten Sie, dass "First()' kann * billiger * sein, obwohl - vermeidet, ein zweites Element zu finden - so kommt es an, ob Sie "mindestens eine" gegen "genau eine" behaupten wollen –

9

Mit Yield können Sie Methoden erstellen, mit denen Daten erzeugt werden, ohne dass vor dem Zurückkehren alles gesammelt werden muss. Stellen Sie sich vor, dass es mehrere Werte auf dem Weg zurückgibt.

Hier ein paar Methoden, die den Punkt

public IEnumerable<String> LinesFromFile(String fileName) 
{ 
    using (StreamReader reader = new StreamReader(fileName)) 
    { 
     String line; 
     while ((line = reader.ReadLine()) != null) 
      yield return line; 
    } 
} 

public IEnumerable<String> LinesWithEmails(IEnumerable<String> lines) 
{ 
    foreach (String line in lines) 
    { 
     if (line.Contains("@")) 
      yield return line; 
    } 
} 

illustrieren Keine dieser beiden Methoden wird der gesamte Inhalt der Datei in den Speicher gelesen, aber man kann sie wie folgt verwenden:

foreach (String lineWithEmail in LinesWithEmails(LinesFromFile("test.txt"))) 
    Console.Out.WriteLine(lineWithEmail); 
3

Mit yield können Sie Sammlungen verarbeiten, deren Größe potenziell unendlich ist, da die gesamte Sammlung im Gegensatz zu einem auf Listen basierenden Ansatz niemals auf einmal in den Arbeitsspeicher geladen wird. Zum Beispiel könnte ein IEnumerable <> von allen Primzahlen durch den entsprechenden Algo zum Auffinden der Primzahlen zurückgenommen werden, wohingegen ein List-Ansatz immer endlich und daher unvollständig wäre. In diesem Beispiel ermöglicht die Verwendung von yield auch die Verarbeitung für das nächste Element, das zurückgestellt werden soll, bis es benötigt wird.

1

Eine echte Situation für mich ist, wenn ich eine Sammlung verarbeiten möchte, die eine Weile dauert, um reibungsloser zu bevölkern.

etwas entlang der Linien (Pseudo-Code) Stellen Sie sich vor:

public IEnumberable<VerboseUserInfo> GetAllUsers() 
{ 
    foreach(UserId in userLookupList) 
    { 
     VerboseUserInfo info = new VerboseUserInfo(); 

     info.Load(ActiveDirectory.GetLotsOfUserData(UserId)); 
     info.Load(WebSerice.GetSomeMoreInfo(UserId)); 

     yield return info; 
    } 
} 

Statt eine Minute für die Sammlung warten zu müssen, füllen, bevor ich Gegenstände darin verarbeiten beginnen können. Ich kann sofort starten und dann zurück zur Benutzeroberfläche berichten, wenn es passiert.

0

Ich stimme allem zu, was jeder hier über faule Auswertung und Speichernutzung gesagt hat und wollte ein weiteres Szenario hinzufügen, in dem ich die Iteratoren mit dem Schlüssel yield nützlich gefunden habe. Ich bin auf einige Fälle gestoßen, in denen ich eine Sequenz von möglicherweise kostspieliger Verarbeitung einiger Daten ausführen muss, bei denen es äußerst nützlich ist, Iteratoren zu verwenden. Anstatt sofort die gesamte Datei verarbeitet werden, bzw. meine eigene Verarbeitungspipeline rollen, kann ich einfach Iteratoren etwas wie folgt verwenden:

IEnumerable<double> GetListFromFile(int idxItem) 
{ 
    // read data from file 
    return dataReadFromFile; 
} 

IEnumerable<double> ConvertUnits(IEnumerable<double> items) 
{ 
    foreach(double item in items) 
     yield return convertUnits(item); 
} 

IEnumerable<double> DoExpensiveProcessing(IEnumerable<double> items) 
{ 
    foreach(double item in items) 
     yield return expensiveProcessing(item); 
} 

IEnumerable<double> GetNextList() 
{ 
    return DoExpensiveProcessing(ConvertUnits(GetListFromFile(curIdx++))); 
} 

Der hier Vorteil ist, dass durch IEnumerable<double> die Ein- und Ausgabe auf alle Funktionen zu halten, meine Verarbeitung Pipeline ist vollständig zusammensetzbar, leicht zu lesen und faul ausgewertet, so dass ich nur die Verarbeitung tun muss, die ich wirklich tun muss. Dadurch kann ich fast alle meine Verarbeitungen in den GUI-Thread einfügen, ohne die Reaktionsfähigkeit zu beeinflussen, so dass ich mich nicht um Threading-Probleme kümmern muss.

1

Sie möchten vielleicht nicht immer yield verwenden, anstatt eine Liste zurückzugeben, und in Ihrem Beispiel verwenden Sie yield, um tatsächlich eine Liste von ganzen Zahlen zurückzugeben. Je nachdem, ob Sie eine veränderbare Liste oder eine unveränderliche Sequenz wünschen, können Sie eine Liste oder einen Iterator (oder eine andere Sammlung mutable/immutable) verwenden.

Aber es gibt Vorteile, Ertrag zu verwenden.

  • Ausbeute bietet eine einfache Möglichkeit, lazy evaluated Iteratoren zu erstellen.(Das heißt, nur der Code zum Abrufen des nächsten Elements wird ausgeführt, wenn die MoveNext() -Methode aufgerufen wird. Der Iterator gibt keine Berechnungen mehr zurück, bis die Methode erneut aufgerufen wird.)

  • Ertrag erzeugt eine Zustandsmaschine unter den Deckeln und das erspart Ihnen viel Arbeit, indem Sie nicht die Zustände Ihres generischen Generators codieren müssen => prägnanter/einfacher Code.

  • Ertrag automatisch erstellt optimierte und Thread sichere Iteratoren, ersparen Sie die Details, wie Sie sie bauen.

  • Ertrag ist viel mächtiger als es auf den ersten Blick scheint und kann für viel mehr als nur einfache Iteratoren verwendet werden, schauen Sie sich dieses Video an Jeffrey Richter and his AsyncEnumerator und wie Ausbeute verwendet wird, machen Codierung mit dem asynchronen Muster einfach.

  • 0

    Ich kam mit diesem um zu überwinden .net Manko, die Liste manuell tief zu kopieren.

    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 generische 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(); 
         } 
        } 
    } 
    
    0

    Das Verfahren von yield der verwendeten durch Speicher sparenden Verarbeitung Artikel on-the-fly ist schön, aber es ist wirklich nur syntaktischer Zucker . Es ist schon lange her. In jeder Sprache, die Funktions- oder Schnittstellenzeiger (auch C und Assembly) hat, können Sie mit einer Callback-Funktion/Schnittstelle denselben Effekt erzielen.

    Diese fancy stuff:

    static IEnumerable<string> GetItems() 
    { 
        yield return "apple"; 
        yield return "orange"; 
        yield return "pear"; 
    } 
    
    foreach(string item in GetItems()) 
    { 
        Console.WriteLine(item); 
    } 
    

    ist im Grunde gleichbedeutend mit altmodischen: [? Was realen Anwendungen der Ausbeute sind]

    interface ItemProcessor 
    { 
        void ProcessItem(string s); 
    }; 
    
    class MyItemProcessor : ItemProcessor 
    { 
        public void ProcessItem(string s) 
        { 
         Console.WriteLine(s); 
        } 
    }; 
    
    static void ProcessItems(ItemProcessor processor) 
    { 
        processor.ProcessItem("apple"); 
        processor.ProcessItem("orange"); 
        processor.ProcessItem("pear"); 
    } 
    
    ProcessItems(new MyItemProcessor()); 
    
    +0

    Es ist nicht wirklich gleichwertig, da jeder Ansatz erlaubt Dinge, die im anderen nicht gemacht werden können. Zum Beispiel ist es möglich, zwei IEnumerables "parallel" für so etwas wie eine Zusammenführungsoperation zu iterieren; So etwas wäre mit dem Delegate-Passing-Ansatz nicht möglich. Auf der anderen Seite wäre es für eine "DoForEach" -Stil-Methode wie ProcessItems möglich, einen Parameter per Referenz zu akzeptieren und ihn als Referenz an den verschachtelten Delegaten zu übergeben; Solche Methoden umschließen auch die verschachtelten Item-Aufrufe in "try" -Blöcken und müssen sich nicht darum sorgen, ohne Disposed verlassen zu werden. – supercat