981 Stimmen

Richtige Verwendung von "Rendite

El Ertrag ist eines dieser Schlüsselwörter Schlüsselwörter in C#, die mich immer wieder vor ein Rätsel stellt, und ich war mir nie sicher, ob ich sie richtig benutze.

Welcher der beiden folgenden Codes ist der bessere und warum?

Version 1: Verwendung der Ertragsrendite

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: Rückgabe der Liste

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

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

861voto

abelenky Punkte 60187

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

Bei Ihrer Version 2 müssen Sie vor der Rückgabe die vollständige Liste haben. Bei der Verwendung von "yield-return" müssen Sie wirklich nur den nächsten Posten haben, bevor Sie zurückkehren.

Dies trägt unter anderem dazu bei, die Rechenkosten komplexer Berechnungen auf einen größeren Zeitrahmen zu verteilen. Wenn die Liste beispielsweise mit einer grafischen Benutzeroberfläche verbunden ist und der Benutzer nie die letzte Seite aufruft, werden die letzten Elemente der Liste nicht berechnet.

Ein weiterer Fall, in dem yield-return vorzuziehen ist, ist, wenn die IEnumerable eine unendliche Menge darstellt. Denken Sie an die Liste der Primzahlen oder an eine unendliche Liste von Zufallszahlen. Sie können nie die gesamte IEnumerable auf einmal zurückgeben, also verwenden Sie yield-return, um die Liste schrittweise zurückzugeben.

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

732voto

anar khalilov Punkte 15972

Das Auffüllen einer temporären Liste ist wie das Herunterladen des gesamten Videos, während die Verwendung von yield ist wie das Streamen dieses Videos.

82voto

Kache Punkte 12983

Als konzeptionelles Beispiel für das Verständnis, wann Sie Folgendes verwenden sollten yield sagen wir, die Methode ConsumeLoop() verarbeitet die zurückgegebenen/ergebenen Artikel von ProduceList() :

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 der Aufruf an ProduceList() kann lange dauern, weil Sie die Liste vor der Rückkehr vervollständigen müssen:

//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]

Verwendung von yield wird es neu geordnet, sozusagen verschachtelt:

//pseudo-assembly
Produce consumable[0]
Consume consumable[0]                   // immediately yield & Consume
Produce consumable[1]                   // ConsumeLoop iterates, requesting next item
Consume consumable[1]                   // consume next
Produce consumable[2]
Consume consumable[2]                   // consume next
Produce consumable[3]
Consume consumable[3]                   // consume next

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

37voto

Adam W. McKinley Punkte 745

Ich weiß, dass dies eine alte Frage ist, aber ich möchte ein Beispiel dafür geben, wie das Schlüsselwort yield kreativ eingesetzt werden kann. Ich habe vraiment von dieser Technik profitiert. Ich hoffe, dass dies für alle anderen, die über diese Frage stolpern, eine Hilfe ist.

Hinweis: Betrachten Sie das Schlüsselwort yield nicht nur als eine weitere Möglichkeit, eine Sammlung aufzubauen. Ein großer Teil der Macht von yield liegt in der Tatsache, dass die Ausführung pausiert in Ihrem Methode oder Eigenschaft, bis der aufrufende Code über den nächsten Wert iteriert. Hier ist mein Beispiel:

Die Verwendung des Schlüsselworts yield (zusammen mit Rob Eisenburgs Caliburn.Micro-Koroutinen Implementierung) ermöglicht es mir, einen asynchronen Aufruf an einen Webdienst wie diesen auszudrü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();
}

Dies schaltet meinen BusyIndicator ein, ruft die Login-Methode meines Webdienstes auf, setzt mein IsLoggedIn-Flag auf den Rückgabewert und schaltet den BusyIndicator dann wieder aus.

Das funktioniert folgendermaßen: IResult hat eine Execute-Methode und ein Completed-Ereignis. Caliburn.Micro greift sich 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 innerhalb von HandleButtonClick() angehalten, und BeginExecute() hängt einen Ereignishandler an das Ereignis Completed an und ruft Execute() auf. IResult.Execute() kann entweder eine synchrone oder eine asynchrone Aufgabe ausführen und feuert das Ereignis Completed ab, wenn es fertig ist.

LoginResult sieht in etwa 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 Schritt für Schritt zu beobachten.

Ich hoffe, das hilft jemandem weiter! Es hat mir wirklich Spaß gemacht, die verschiedenen Verwendungsmöglichkeiten von Rendite zu erforschen.

22voto

Andzej Maciusovic Punkte 3958

Yield Return kann für Algorithmen, bei denen Millionen von Objekten durchlaufen werden müssen, sehr leistungsfähig sein. Betrachten Sie das folgende Beispiel, bei dem Sie mögliche Fahrten für Mitfahrgelegenheiten berechnen müssen. Zunächst generieren wir mögliche Fahrten:

    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())
        {
            // 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 1 Million Objekte im Speicher zuweisen (~190mb) und die Ausführung dieses einfachen Beispiels wird ~1400ms dauern. Wenn Sie jedoch Yield verwenden, müssen Sie all diese temporären Objekte nicht im Speicher ablegen und Sie erhalten eine deutlich schnellere Algorithmusgeschwindigkeit: Dieses Beispiel benötigt nur ~400 ms für die Ausführung ohne jeglichen Speicherverbrauch.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X