it-swarm.com.de

Richtige Verwendung von "Yield Return"

Das Schlüsselwort yield ist eines der Schlüsselwörter keywords in C #, das mich weiterhin verwirrt, und ich war mir nie sicher, dass ich es richtig verwende.

Welche der folgenden beiden Codeteile wird bevorzugt und warum?

Version 1: Verwenden von Yield Return

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        foreach (Product product in products)
        {
            yield return product;
        }
    }
}

Version 2: Liefert die Liste

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        return products.ToList<Product>();
    }
}
853
senfo

Ich neige dazu, Rendite-Rendite zu verwenden, wenn ich den nächsten Artikel in der Liste (oder sogar die nächste Artikelgruppe) berechne.

Bei Verwendung von Version 2 muss die vollständige Liste vorliegen, bevor Sie zurückkehren können. Wenn Sie yield-return verwenden, müssen Sie wirklich nur den nächsten Artikel haben, bevor Sie zurückkehren.

Dies trägt unter anderem dazu bei, die Berechnungskosten für komplexe Berechnungen über einen größeren Zeitraum zu verteilen. Wenn die Liste beispielsweise mit einer grafischen Benutzeroberfläche verbunden ist und der Benutzer nie zur letzten Seite wechselt, werden die endgültigen Elemente in der Liste nie berechnet.

Ein anderer Fall, in dem Yield-Return vorzuziehen ist, ist, wenn IEnumerable eine unendliche Menge darstellt. Betrachten Sie die Liste der Primzahlen oder eine unendliche Liste von Zufallszahlen. Sie können niemals die vollständige IEnumerable auf einmal zurückgeben. Verwenden Sie daher yield-return, um die Liste inkrementell zurückzugeben.

In Ihrem speziellen Beispiel haben Sie die vollständige Liste der Produkte, daher würde ich Version 2 verwenden.

774
abelenky

Das Auffüllen einer temporären Liste entspricht dem Herunterladen des gesamten Videos, während die Verwendung von yield dem Streamen dieses Videos gleicht.

602
anar khalilov

Als konzeptionelles Beispiel für das Verständnis, wann Sie yield verwenden sollten, nehmen wir an, dass die Methode ConsumeLoop() die von ProduceList() zurückgegebenen/ausgegebenen Elemente verarbeitet:

void ConsumeLoop() {
    foreach (Consumable item in ProduceList())        // might have to wait here
        item.Consume();
}

IEnumerable<Consumable> ProduceList() {
    while (KeepProducing())
        yield return ProduceExpensiveConsumable();    // expensive
}

Ohne yield kann der Aufruf von ProduceList() lange dauern, da Sie die Liste vervollständigen müssen, bevor Sie zurückkehren:

//pseudo-Assembly
Produce consumable[0]                   // expensive operation, e.g. disk I/O
Produce consumable[1]                   // waiting...
Produce consumable[2]                   // waiting...
Produce consumable[3]                   // completed the consumable list
Consume consumable[0]                   // start consuming
Consume consumable[1]
Consume consumable[2]
Consume consumable[3]

Mit yield wird es neu angeordnet und arbeitet "parallel":

//pseudo-Assembly
Produce consumable[0]
Consume consumable[0]                   // immediately Consume
Produce consumable[1]
Consume consumable[1]                   // consume next
Produce consumable[2]
Consume consumable[2]                   // consume next
Produce consumable[3]
Consume consumable[3]                   // consume next

Und schließlich sollten Sie, wie bereits von vielen vorgeschlagen, Version 2 verwenden, da Sie die vollständige Liste ohnehin bereits haben.

67
Kache

Dies scheint ein bizarrer Vorschlag zu sein, aber ich habe gelernt, wie man das Schlüsselwort yield in C # verwendet, indem ich eine Präsentation über Generatoren in Python gelesen habe: David M. Beazley's http: //www.dabeaz. de/generators/Generators.pdf . Sie müssen nicht viel Python wissen, um die Präsentation zu verstehen - ich habe es nicht getan. Ich fand es sehr hilfreich, nicht nur zu erklären, wie Generatoren funktionieren, sondern auch, warum Sie sich darum kümmern sollten.

26
Robert Rossney

Ich weiß, dass dies eine alte Frage ist, aber ich möchte ein Beispiel dafür geben, wie das Ertrags-Keyword kreativ verwendet werden kann. Ich habe wirklich von dieser Technik profitiert. Hoffentlich hilft dies allen, die über diese Frage stolpern.

Hinweis: Betrachten Sie das Schlüsselwort yield nicht als eine weitere Möglichkeit, eine Sammlung zu erstellen. Ein großer Teil der Ertragskraft ergibt sich aus der Tatsache, dass die Ausführung in Ihrer Methode oder Eigenschaft pausiert ​​ist, bis der aufrufende Code über den nächsten Wert iteriert. Hier ist mein Beispiel:

Mit dem Schlüsselwort yield (neben Rob Eisenburgs Implementierung Caliburn.Micro coroutines ) kann ich einen asynchronen Aufruf eines Webdienstes wie folgt ausdrücken:

public IEnumerable<IResult> HandleButtonClick() {
    yield return Show.Busy();

    var loginCall = new LoginResult(wsClient, Username, Password);
    yield return loginCall;
    this.IsLoggedIn = loginCall.Success;

    yield return Show.NotBusy();
}

Dadurch wird mein BusyIndicator aktiviert, die Anmeldemethode für meinen Webdienst aufgerufen, das IsLoggedIn-Flag auf den Rückgabewert gesetzt und dann der BusyIndicator wieder deaktiviert.

So funktioniert das: IResult verfügt über eine Execute-Methode und ein Completed-Ereignis. Caliburn.Micro erfasst den IEnumerator aus dem Aufruf von HandleButtonClick () und übergibt ihn an eine Coroutine.BeginExecute-Methode. Die BeginExecute-Methode beginnt mit der Iteration durch die IResults. Wenn das erste IResult zurückgegeben wird, wird die Ausführung in HandleButtonClick () angehalten, und BeginExecute () hängt einen Ereignishandler an das Completed-Ereignis an und ruft Execute () auf. IResult.Execute () kann entweder eine synchrone oder eine asynchrone Aufgabe ausführen und löst das Completed-Ereignis aus, wenn es abgeschlossen ist.

LoginResult sieht ungefähr so ​​aus:

public LoginResult : IResult {
    // Constructor to set private members...

    public void Execute(ActionExecutionContext context) {
        wsClient.LoginCompleted += (sender, e) => {
            this.Success = e.Result;
            Completed(this, new ResultCompletionEventArgs());
        };
        wsClient.Login(username, password);
    }

    public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
    public bool Success { get; private set; }
}

Es kann hilfreich sein, so etwas einzurichten und die Ausführung schrittweise zu durchlaufen, um zu beobachten, was los ist.

Hoffe das hilft jemandem aus! Ich habe es wirklich genossen, die verschiedenen Möglichkeiten zu erkunden, wie Ertrag verwendet werden kann.

26

Rendite kann für Algorithmen, bei denen Sie Millionen von Objekten durchlaufen müssen, sehr leistungsfähig sein. Betrachten Sie das folgende Beispiel, in dem Sie mögliche Fahrten für Mitfahrgelegenheiten berechnen müssen. Zuerst generieren wir mögliche Reisen:

    static IEnumerable<Trip> CreatePossibleTrips()
    {
        for (int i = 0; i < 1000000; i++)
        {
            yield return new Trip
            {
                Id = i.ToString(),
                Driver = new Driver { Id = i.ToString() }
            };
        }
    }

Dann durchlaufen Sie jede Reise:

    static void Main(string[] args)
    {
        foreach (var trip in CreatePossibleTrips(trips))
        {
            // possible trip is actually calculated only at this point, because of yield
            if (IsTripGood(trip))
            {
                // match good trip
            }
        }
    }

Wenn Sie List anstelle von yield verwenden, müssen Sie dem Speicher 1 Million Objekte zuweisen (~ 190 MB). Die Ausführung dieses einfachen Beispiels dauert ~ 1400 ms. Wenn Sie jedoch yield verwenden, müssen Sie nicht alle diese temporären Objekte im Speicher ablegen, und Sie erhalten eine erheblich schnellere Algorithmusgeschwindigkeit: Für die Ausführung dieses Beispiels werden nur ~ 400 ms benötigt, ohne dass Speicher belegt wird.

13

Die zwei Teile des Codes machen wirklich zwei verschiedene Dinge. In der ersten Version werden Mitglieder nach Bedarf abgerufen. Die zweite Version lädt alle Ergebnisse in den Speicher bevor Sie beginnen, etwas damit zu tun.

Es gibt keine richtige oder falsche Antwort auf diese Frage. Welches gerade vorzuziehen ist, hängt von der Situation ab. Wenn Sie beispielsweise eine zeitliche Begrenzung für die Fertigstellung Ihrer Abfrage haben und mit den Ergebnissen etwas Halbkompliziertes anfangen müssen, ist die zweite Version möglicherweise vorzuziehen. Achten Sie jedoch auf große Ergebnismengen, insbesondere wenn Sie diesen Code im 32-Bit-Modus ausführen. Ich bin bei dieser Methode mehrmals von OutOfMemory-Ausnahmen gebissen worden.

Das Wichtigste ist jedoch, dass die Unterschiede in der Effizienz liegen. Aus diesem Grund sollten Sie sich wahrscheinlich für einen einfacheren Code entscheiden und diesen erst nach der Profilerstellung ändern.

12
Jason Baker

Ertrag hat zwei große Verwendungen

Es ist hilfreich, eine benutzerdefinierte Iteration bereitzustellen, ohne temporäre Sammlungen zu erstellen. (Laden aller Daten und Schleifen)

Es hilft, eine zustandsbehaftete Iteration durchzuführen. (Streaming)

Unten ist ein einfaches Video, das ich mit vollständiger Demonstration erstellt habe, um die obigen zwei Punkte zu unterstützen

http://www.youtube.com/watch?v=4fju3xcm21M

11

Dies ist, was Chris Sells über diese Aussagen in The C # Programming Language ;

Ich vergesse manchmal, dass Yield Return nicht dasselbe ist wie Return, da der Code nach einer Yield Return ausgeführt werden kann. Beispielsweise kann der Code nach der ersten Rückkehr hier niemals ausgeführt werden:

    int F() {
return 1;
return 2; // Can never be executed
}

Im Gegensatz dazu kann hier der Code nach der ersten Yield Return ausgeführt werden:

IEnumerable<int> F() {
yield return 1;
yield return 2; // Can be executed
}

Das beißt mich oft in eine if-Anweisung:

IEnumerable<int> F() {
if(...) { yield return 1; } // I mean this to be the only
// thing returned
yield return 2; // Oops!
}

In diesen Fällen ist es hilfreich, sich daran zu erinnern, dass die Rendite nicht wie die Rendite „endgültig“ ist.

9
Teoman shipahi

Angenommen, die LINQ-Klasse für Ihre Produkte verwendet eine ähnliche Ausbeute für die Aufzählung/Iteration, ist die erste Version effizienter, da sie bei jedem Durchlauf nur einen Wert liefert.

Das zweite Beispiel konvertiert den Enumerator/Iterator mit der ToList () -Methode in eine Liste. Dies bedeutet, dass alle Elemente im Enumerator manuell durchlaufen werden und anschließend eine flache Liste zurückgegeben wird.

8
Soviut

Dies ist ein bisschen neben dem Punkt, aber da die Frage mit Best Practices gekennzeichnet ist, werde ich fortfahren und meine zwei Cent einwerfen. Für diese Art von Dingen bevorzuge ich es sehr, eine Immobilie daraus zu machen:

public static IEnumerable<Product> AllProducts
{
    get {
        using (AdventureWorksEntities db = new AdventureWorksEntities()) {
            var products = from product in db.Product
                           select product;

            return products;
        }
    }
}

Sicher, es ist ein bisschen mehr Boiler-Platte, aber der Code, der dies verwendet, wird viel sauberer aussehen:

prices = Whatever.AllProducts.Select (product => product.price);

vs

prices = Whatever.GetAllProducts().Select (product => product.price);

Anmerkung: Ich würde dies nicht für Methoden tun, die eine Weile brauchen, um ihre Arbeit zu erledigen.

8

Und was ist damit?

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        return products.ToList();
    }
}

Ich denke, das ist viel sauberer. Ich habe VS2008 jedoch nicht zur Hand, um es zu überprüfen. Wenn Products IEnumerable implementiert (wie es scheint - es wird in einer foreach-Anweisung verwendet), würde ich es auf jeden Fall direkt zurückgeben.

7
petr k.

In diesem Fall hätte ich Version 2 des Codes verwendet. Da Sie über die vollständige Liste der verfügbaren Produkte verfügen und dies vom "Verbraucher" dieses Methodenaufrufs erwartet wird, müssen Sie die vollständigen Informationen an den Anrufer zurücksenden.

Wenn der Aufrufer dieser Methode "eine" Information nach der anderen benötigt und der Verbrauch der nächsten Information auf Abruf erfolgt, wäre es vorteilhaft, Yield Return zu verwenden, wodurch sichergestellt wird, dass der Ausführungsbefehl an den Aufrufer zurückgegeben wird, wenn Eine Informationseinheit ist verfügbar.

Einige Beispiele, bei denen man Yield Return verwenden könnte, sind:

  1. Komplexe, schrittweise Berechnung, bei der der Anrufer jeweils auf die Daten eines Schritts wartet
  2. Paging in der grafischen Benutzeroberfläche: Der Benutzer gelangt möglicherweise nie zur letzten Seite, und auf der aktuellen Seite müssen nur Teilinformationen veröffentlicht werden

Um Ihre Fragen zu beantworten, hätte ich die Version 2 verwendet.

5

Senden Sie die Liste direkt zurück. Leistungen:

  • Es ist klarer
  • Die Liste ist wiederverwendbar. (der Iterator ist nicht) trifft eigentlich nicht zu, danke Jon

Sie sollten den Iterator (yield) verwenden, wenn Sie glauben, dass Sie wahrscheinlich nicht bis zum Ende der Liste iterieren müssen oder wenn es kein Ende gibt. Zum Beispiel sucht der Client, der aufruft, nach dem ersten Produkt, das ein Prädikat erfüllt. Sie können auch den Iterator verwenden, obwohl dies ein erfundenes Beispiel ist und es wahrscheinlich bessere Möglichkeiten gibt, dies zu erreichen. Wenn Sie im Voraus wissen, dass die gesamte Liste berechnet werden muss, müssen Sie dies im Voraus tun. Wenn Sie der Meinung sind, dass dies nicht der Fall ist, sollten Sie die Iterator-Version verwenden.

3
recursive

Die Schlüsselphrase yield return wird verwendet, um den Zustandsautomaten für eine bestimmte Sammlung zu verwalten. Überall dort, wo die CLR die Yield Return-Schlüsselphrase verwendet, implementiert die CLR ein Enumerator-Muster für diesen Code. Diese Art der Implementierung hilft dem Entwickler bei jeder Art von Installation, die wir ohne das Schlüsselwort ausführen müssten.

Angenommen, der Entwickler filtert eine Sammlung, durchläuft die Sammlung und extrahiert diese Objekte in einer neuen Sammlung. Diese Art der Installation ist ziemlich eintönig.

Mehr über das Schlüsselwort hier in diesem Artikel .

1
Vikram