Question Utilisation appropriée du «rendement du rendement»


le rendement mot-clé est l'un de ceux mots clés en C # qui continue à me mystifier, et je n'ai jamais été sûr que je l'utilise correctement.

Parmi les deux morceaux de code suivants, lequel est le préféré et pourquoi?

Version 1: Utilisation du rendement du rendement

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: Retourner la liste

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

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

792
2018-01-03 22:43


origine


Réponses:


J'ai tendance à utiliser rendement-rendement lorsque je calcule l'élément suivant dans la liste (ou même le groupe d'éléments suivant).

En utilisant votre version 2, vous devez avoir la liste complète avant de revenir. En utilisant yield-return, vous avez seulement besoin d'avoir l'élément suivant avant de revenir.

Entre autres choses, cela aide à répartir le coût de calcul des calculs complexes sur une période plus longue. Par exemple, si la liste est connectée à une interface graphique et que l'utilisateur ne va jamais à la dernière page, vous ne calculez jamais les éléments finaux dans la liste.

Un autre cas où yield-return est préférable est si IEnumerable représente un ensemble infini. Considérons la liste des nombres premiers, ou une liste infinie de nombres aléatoires. Vous ne pouvez jamais renvoyer l'intégralité de IEnumerable en une seule fois, vous utilisez donc yield-return pour renvoyer la liste de manière incrémentielle.

Dans votre exemple, vous avez la liste complète des produits, donc j'utiliserais la version 2.


725
2018-01-03 23:01



Remplir une liste temporaire revient à télécharger l'intégralité de la vidéo, yield c'est comme diffuser cette vidéo.


535
2017-07-08 14:14



Comme un exemple conceptuel pour comprendre quand vous devriez utiliser yield, disons la méthode ConsumeLoop() traite les articles retournés / cédés par ProduceList():

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

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

Sans pour autant yield, l'appel à ProduceList() peut prendre beaucoup de temps car vous devez compléter la liste avant de revenir:

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

En utilisant yield, il devient réarrangé, sorte de travail "en parallèle":

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

Et enfin, comme vous l'avez déjà suggéré, vous devriez utiliser la version 2 parce que vous avez déjà la liste complète.


59
2017-10-09 21:45



Cela va sembler une suggestion bizarre, mais j'ai appris comment utiliser le yield mot-clé en C # en lisant une présentation sur les générateurs en Python: David M. Beazley http://www.dabeaz.com/generators/Generators.pdf. Vous n'avez pas besoin de connaître beaucoup Python pour comprendre la présentation - je n'ai pas. Je l'ai trouvé très utile pour expliquer non seulement comment les générateurs fonctionnent mais pourquoi vous devriez vous en soucier.


26
2018-01-04 21:51



Je sais que c'est une vieille question, mais j'aimerais vous donner un exemple de la façon dont le mot-clé yield peut être utilisé de façon créative. j'ai vraiment bénéficié de cette technique. J'espère que cela sera utile à toute personne qui trébuche sur cette question.

Remarque: Ne pensez pas que le mot-clé yield est simplement une autre façon de créer une collection. Une grande partie de la puissance du rendement vient du fait que l'exécution est mis en pause dans ton méthode ou propriété jusqu'à ce que le code appelant itère sur la valeur suivante. Voici mon exemple:

Utilisation du mot-clé yield (aux côtés de Rob Eisenburg Caliburn.Micro coroutinesimplémentation) me permet d'exprimer un appel asynchrone à un service web comme celui-ci:

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

Ce que cela va faire est allumer mon BusyIndicator, appelez la méthode de connexion sur mon service Web, définissez mon indicateur IsLoggedIn à la valeur de retour, puis réactivez le BusyIndicator.

Voici comment cela fonctionne: IResult a une méthode Execute et un événement Completed. Caliburn.Micro saisit l'IEnumerator de l'appel à HandleButtonClick () et le passe dans une méthode Coroutine.BeginExecute. La méthode BeginExecute démarre l'itération via les IResults. Lorsque le premier IResult est renvoyé, l'exécution est suspendue dans HandleButtonClick () et BeginExecute () attache un gestionnaire d'événements à l'événement Completed et appelle Execute (). IResult.Execute () peut effectuer une tâche synchrone ou asynchrone et déclenche l'événement Completed lorsqu'il est terminé.

LoginResult ressemble à ceci:

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; }
}

Cela peut aider à mettre en place quelque chose comme ça et passer à travers l'exécution pour regarder ce qui se passe.

J'espère que cela aide quelqu'un! J'ai vraiment aimé explorer les différentes façons dont le rendement peut être utilisé.


24
2018-05-18 04:36



Les deux morceaux de code font vraiment deux choses différentes. La première version attirera les membres selon vos besoins. La deuxième version chargera tous les résultats en mémoire avant vous commencez à faire n'importe quoi avec ça.

Il n'y a pas de bonne ou de mauvaise réponse à celle-ci. Lequel est préférable dépend de la situation. Par exemple, s'il y a une limite de temps que vous devez remplir votre requête et que vous devez faire quelque chose de semi-compliqué avec les résultats, la deuxième version pourrait être préférable. Mais attention aux gros résultats, surtout si vous utilisez ce code en mode 32 bits. J'ai été mordu par des exceptions OutOfMemory plusieurs fois en faisant cette méthode.

La chose clé à garder à l'esprit est la suivante: les différences sont dans l'efficacité. Ainsi, vous devriez probablement aller avec celui qui rend votre code plus simple et le changer seulement après le profilage.


12
2018-01-03 23:30



Le rendement a deux grandes utilisations

Il permet de fournir une itération personnalisée sans créer de collections temporaires. (chargement de toutes les données et en boucle)

Cela aide à faire une itération avec état. ( diffusion)

Voici une vidéo simple que j'ai créée avec une démonstration complète afin de soutenir les deux points ci-dessus

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


10
2018-04-05 18:48



C'est quoi Chris vend parle de ces déclarations dans Le langage de programmation C #;

J'oublie parfois que le rendement du rendement n'est pas le même que le rendement,   que le code après un retour de rendement peut être exécuté. Par exemple, le   code après le premier retour ici ne peut jamais être exécuté:

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

En revanche, le code après le premier retour de rendement ici peut être   réalisé:

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

Cela me mord souvent dans une déclaration if:

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

Dans ces cas, se rappeler que le rendement du rendement n'est pas "final" comme   le retour est utile.


10
2018-05-27 20:18



Le rendement de rendement peut être très puissant pour les algorithmes où vous devez parcourir des millions d'objets. Considérez l'exemple suivant où vous devez calculer les trajets possibles pour le covoiturage. D'abord nous générons des voyages possibles:

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

Ensuite, parcourez chaque voyage:

    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
            }
        }
    }

Si vous utilisez List au lieu de yield, vous devrez allouer 1 million d'objets à la mémoire (~ 190mb) et cet exemple simple prendra ~ 1400ms. Cependant, si vous utilisez yield, vous n'avez pas besoin de mettre tous ces objets temp dans la mémoire et vous obtiendrez une vitesse d'algorithme significativement plus rapide: cet exemple ne prendra que ~ 400ms sans aucune consommation de mémoire.


9
2017-11-22 16:35



En supposant que la classe LINQ de vos produits utilise un rendement similaire pour l'énumération / l'itération, la première version est plus efficace car elle ne rapporte qu'une seule valeur chaque fois qu'elle est répétée.

Le deuxième exemple convertit l'énumérateur / itérateur en une liste avec la méthode ToList (). Cela signifie qu'il itère manuellement sur tous les éléments de l'énumérateur, puis renvoie une liste à plat.


8
2018-01-03 22:58



C'est un peu hors sujet, mais puisque la question est étiquetée meilleures pratiques, je vais aller de l'avant et jeter dans mes deux cents. Pour ce genre de chose, je préfère grandement en faire une propriété:

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

            return products;
        }
    }
}

Bien sûr, c'est un peu plus chaud-plaque, mais le code qui utilise cela sera beaucoup plus propre:

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

contre

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

Remarque: Je ne ferais pas cela pour toutes les méthodes qui peuvent prendre un certain temps pour faire leur travail.


8
2018-01-03 23:30