Question

Le mot clé rendement est l'un des suivants. Ces mots clés en C # qui continuent de me mystifier, et je n'ai jamais été sûr de l'utiliser correctement.

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

Version 1: Utilisation du retour de 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: renvoyer 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>();
    }
}
Était-ce utile?

La solution

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

Avec votre version 2, vous devez avoir la liste complète avant de retourner. En utilisant return-return, vous n’avez vraiment besoin que du prochain article avant de retourner.

Cela permet notamment de répartir le coût de calcul de calculs complexes sur une période plus longue. Par exemple, si la liste est connectée à une interface graphique et que l'utilisateur ne passe jamais à la dernière page, vous ne calculez jamais les éléments finaux de la liste.

Un autre cas où rendement / rendement est préférable est le cas où IEnumerable représente un ensemble infini. Considérez la liste des nombres premiers ou une liste infinie de nombres aléatoires. Vous ne pouvez jamais renvoyer l'intégralité de IEnumerable à la fois. Vous devez donc utiliser return-return pour renvoyer la liste de manière incrémentielle.

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

Autres conseils

Remplir une liste temporaire équivaut à télécharger la vidéo complète, tandis que l’utilisation de rendement revient à diffuser cette vidéo en streaming.

Comme exemple conceptuel pour comprendre quand vous devez utiliser rendement , supposons que la méthode ConsumeLoop () traite les éléments renvoyés / produits 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 return , l'appel de ProduceList () peut prendre un certain temps car vous devez compléter la liste avant de renvoyer:

//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 est réorganisé, en quelque sorte "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

Enfin, comme beaucoup d’entre vous l’ont déjà suggéré, vous devez utiliser la version 2, car vous avez déjà la liste complète.

Cela va sembler une suggestion bizarre, mais j’ai appris à utiliser le mot clé yield en C # en lisant une présentation sur les générateurs en Python: http://www.dabeaz.com/generators/Generators.pdf . Vous n'avez pas besoin de connaître beaucoup Python pour comprendre la présentation - je ne l'ai pas fait. J'ai trouvé très utile d'expliquer non seulement le fonctionnement des générateurs, mais également la raison pour laquelle vous devriez vous en soucier.

Je sais que c’est une vieille question, mais j’aimerais donner un exemple de la manière dont le mot clé rendement peut être utilisé de manière créative. J'ai vraiment bénéficié de cette technique. J'espère que cela sera utile à quiconque trébuche sur cette question.

Remarque: Ne pensez pas que le mot-clé de rendement est simplement un autre moyen de constituer une collection. Une grande partie de la puissance du rendement réside dans le fait que l’exécution est en pause dans votre méthode ou propriété jusqu'à ce que le code appelant itère sur la valeur suivante. Voici mon exemple:

Utilisation du mot-clé de rendement (à côté de celui de Rob Eisenburg implémentation de Caliburn.Micro coroutines ) me permet d’exprimer un appel asynchrone à un service Web tel que:

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

Cela va activer mon BusyIndicator, appeler la méthode de connexion sur mon service Web, définir mon indicateur IsLoggedIn sur la valeur de retour, puis éteindre le BusyIndicator.

Voici comment cela fonctionne: IResult a une méthode Execute et un événement Completed. Caliburn.Micro récupère l'IEnumerator à partir de l'appel de HandleButtonClick () et le transmet à une méthode Coroutine.BeginExecute. La méthode BeginExecute commence à parcourir les résultats IR. Lorsque le premier résultat IResult est renvoyé, l'exécution est suspendue dans HandleButtonClick () et BeginExecute () attache un gestionnaire d'événement à l'événement Completed et appelle Execute (). IResult.Execute () peut exécuter 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; }
}

Il peut être utile de configurer quelque chose comme ceci et de suivre l'exécution pour voir ce qui se passe.

J'espère que cela aide quelqu'un! J'ai vraiment aimé explorer les différentes manières d'utiliser le rendement.

Yield return peut être très puissant pour les algorithmes dans lesquels vous devez parcourir des millions d'objets. Prenons l'exemple suivant où vous devez calculer des trajets possibles pour un covoiturage. Nous générons d’abord des trajets 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() }
            };
        }
    }

Puis 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 Liste au lieu de rendement, vous devrez affecter 1 million d'objets à la mémoire (~ 190 Mo) et cet exemple simple prendra environ 1400 ms à exécuter. Cependant, si vous utilisez le rendement, vous n'avez pas besoin de mettre tous ces objets temporaires en mémoire et vous obtiendrez une vitesse d'algorithme considérablement plus rapide: cet exemple ne prendra que ~ 400 ms pour s'exécuter sans aucune consommation de mémoire.

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 dans la mémoire avant de commencer à faire quoi que ce soit avec elle.

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 vous reste une limite de temps pour compléter votre requête et que vous devez faire quelque chose de semi-compliqué avec les résultats, la deuxième version peut être préférable. Mais méfiez-vous des résultats volumineux, en particulier si vous exécutez ce code en mode 32 bits. J'ai été mordu par des exceptions OutOfMemory à plusieurs reprises lors de l'utilisation de cette méthode.

Le point essentiel à garder à l’esprit est toutefois le suivant: les différences concernent l’efficacité. Ainsi, vous devriez probablement choisir celui qui simplifie votre code et ne le modifiez qu’après le profilage.

Le rendement a deux excellentes utilisations

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

Il est utile de faire une itération avec état. (streaming)

Vous trouverez ci-dessous une vidéo simple que j'ai créée avec une démonstration complète afin de prendre en charge les deux points ci-dessus

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

C’est ce que Chris Sells parle de ces déclarations dans Le langage de programmation C # ;

  

j’oublie parfois que le retour de rendement n’est pas la même chose que le retour,   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   exécuté:

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

Cela 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, rappelez-vous que le rendement n'est pas "final" comme   le retour est utile.

En supposant que vos produits LINQ utilisent un rendement similaire pour énumérer / itérer, la première version est plus efficace car elle ne donne qu'une valeur à chaque fois qu'elle est itéré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 simple.

C’est un peu différent du but, mais comme la question est balisée comme meilleure pratique, je vais y aller et ajouter mes deux sous. 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, mais le code qui l'utilisera aura l'air beaucoup plus propre:

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

vs

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

Remarque: je ne le ferais pas pour les méthodes qui pourraient prendre un certain temps à faire leur travail.

Et qu'en est-il de cela?

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

        return products.ToList();
    }
}

Je suppose que c'est beaucoup plus propre. Je n'ai pas de VS2008 sous la main pour vérifier, cependant. Dans tous les cas, si Products implémente IEnumerable (comme cela semble être le cas, il est utilisé dans une instruction foreach), je le renverrais directement.

J'aurais utilisé la version 2 du code dans ce cas. Puisque vous avez la liste complète des produits disponibles et que c’est ce à quoi s'attend le "consommateur" de cet appel de méthode, il serait nécessaire de renvoyer les informations complètes à l'appelant.

Si l'appelant de cette méthode nécessite "un" informations à la fois et la consommation des informations suivantes est à la demande, il serait alors avantageux d’utiliser le rendement (return return) qui garantira que la commande d’exécution sera renvoyée à l’appelant lorsqu’une unité d’information est disponible.

Voici quelques exemples d'utilisation du rendement:

  1. Calcul complexe pas à pas lorsque l'appelant attend les données d'une étape à la fois
  2. Recherche dans l'interface graphique: l'utilisateur peut ne jamais atteindre la dernière page et ne divulguer que sur un sous-ensemble d'informations sur la page en cours

Pour répondre à vos questions, j'aurais utilisé la version 2.

Renvoie directement la liste. Avantages:

  • c'est plus clair
  • La liste est réutilisable. (l'itérateur n'est pas) pas vraiment vrai, merci Jon

Vous devez utiliser l’itérateur (rendement) à partir du moment où vous pensez que vous n’aurez probablement pas à itérer jusqu’à la fin de la liste, ou s’il n’a pas de fin. Par exemple, le client appelant va rechercher le premier produit qui satisfait un prédicat. Vous pouvez envisager d'utiliser l'itérateur, bien qu'il s'agisse d'un exemple artificiel, et qu'il existe probablement de meilleures méthodes pour le réaliser. Fondamentalement, si vous savez à l'avance que toute la liste devra être calculée, il suffit de le faire dès le départ. Si vous pensez que ce ne sera pas le cas, envisagez d'utiliser la version itérateur.

La phrase clé de retour de rendement est utilisée pour maintenir la machine à états pour une collection particulière. Partout où le CLR voit que la phrase clé de retour de rendement est utilisée, le CLR implémente un motif Enumerator dans ce morceau de code. Ce type d’implémentation aide le développeur de tous les types de tuyauterie que nous aurions dû faire en l’absence du mot clé.

Supposons que si le développeur filtre une collection, itère à travers la collection, puis extrait ces objets dans une nouvelle collection. Ce type de plomberie est assez monotone.

En savoir plus sur le mot clé ici article .

L'utilisation de rendement est similaire au mot clé return , à ceci près qu'il renvoie un générateur . Et l'objet générateur ne traversera qu'une fois .

Le

rendement présente deux avantages:

  1. Vous n'avez pas besoin de lire ces valeurs deux fois;
  2. Vous pouvez obtenir de nombreux nœuds enfants, mais vous n'avez pas besoin de tous les mettre en mémoire.

Il existe un autre explication peut-être vous aider.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top