web-dev-qa-db-fra.com

Difficulté à comprendre le rendement en C #

J'espère obtenir des éclaircissements sur un extrait que j'ai récemment parcouru dans le débogueur, mais que je ne peux tout simplement pas vraiment comprendre.

Je prends un cours C # sur PluralSight et le sujet actuel est sur yield et je renvoie un IEnumerable<T> avec le mot clé.

J'ai cette fonction trop basique qui renvoie une collection IEnumerable de Vendors (Une classe simple avec Id, CompanyName et Email ):

public IEnumerable<Vendor> RetrieveWithIterator()
{
    this.Retrieve(); // <-- I've got a breakpoint here
    foreach(var vendor in _vendors)
    {
        Debug.WriteLine($"Vendor Id: {vendor.VendorId}");
        yield return vendor;
    }
}

Et j'ai ce code dans un test unitaire que j'utilise pour tester la fonction:

var vendorIterator = repository.RetrieveWithIterator(); // <-- Why don't it enter function?
foreach (var item in vendorIterator) // <-- But starts here?
{
    Debug.WriteLine(item);
}
var actual = vendorIterator.ToList();

Ce que je n'arrive vraiment pas à comprendre, et je suis sûr que beaucoup de débutants ont le même problème, c'est pourquoi l'appel initial à RetrieveWithIterator n'initie pas la fonction, mais il commence plutôt quand nous commencer l'itération à travers sa collection IEnumerable retournée (voir les commentaires).

11
geostocker

C'est ce qu'on appelle une exécution différée, yield est paresseux et ne fonctionnera que si nécessaire.

Cela présente de nombreux avantages, dont l'un est que vous pouvez créer des énumérations apparemment infinies:

public IEnumerable<int> InfiniteOnes()
{
     while (true)
         yield 1;
}

Imaginez maintenant que ce qui suit:

var infiniteOnes = InfiniteOnes();

S'exécuterait avec impatience, vous auriez une exception StackOverflow qui viendrait très heureusement.

D'un autre côté, parce que c'est paresseux, vous pouvez faire ce qui suit:

var infiniteOnes = InfiniteOnes();
//.... some code
foreach (var one in infiniteOnes.Take(100)) { ... }

Et ensuite,

foreach (var one in infiniteOnes.Take(10000)) { ... }

Les blocs d'itérateur ne s'exécutent que lorsqu'ils en ont besoin; lorsque l'énumération est itérée, pas avant, pas après.

13
InBetween

De msdn:

Exécution différée

L'exécution différée signifie que l'évaluation d'une expression est retardée jusqu'à ce que sa valeur réalisée soit réellement requise. L'exécution différée peut améliorer considérablement les performances lorsque vous devez manipuler de grandes collections de données, en particulier dans les programmes qui contiennent une série de requêtes ou de manipulations chaînées. Dans le meilleur des cas, l'exécution différée ne permet qu'une seule itération à travers la collection source.

L'exécution différée est prise en charge directement dans le langage C # par le mot clé yield (sous la forme de l'instruction yield-return) lorsqu'il est utilisé dans un bloc d'itérateur. Un tel itérateur doit renvoyer une collection de type IEnumerator ou IEnumerator<T> (ou un type dérivé).

var vendorIterator = repository.RetrieveWithIterator(); // <-- Lets deferred the execution
foreach (var item in vendorIterator) // <-- execute it because we need it
{
    Debug.WriteLine(item);
}
var actual = vendorIterator.ToList();

Désireux vs évaluation paresseuse

Lorsque vous écrivez une méthode qui implémente une exécution différée, vous devez également décider d'implémenter la méthode à l'aide d'une évaluation paresseuse ou d'une évaluation impatiente.

  • Dans l'évaluation paresseuse, un seul élément de la collection source est traité lors de chaque appel à l'itérateur. C'est la manière typique dont les itérateurs sont implémentés.
  • Dans une évaluation avide, le premier appel à l'itérateur entraînera le traitement de l'ensemble de la collection. Une copie temporaire de la collection source peut également être requise.

L'évaluation paresseuse donne généralement de meilleures performances car elle répartit le traitement des frais généraux de manière uniforme tout au long de l'évaluation de la collection et minimise l'utilisation de données temporaires. Bien sûr, pour certaines opérations, il n'y a pas d'autre option que de matérialiser les résultats intermédiaires.

source

4
aloisdg

Il obtiendra les articles lorsque vous les bouclerez en cas de besoin. De cette façon, vous n'avez besoin que des 4 premiers résultats, puis vous cassez, cela ne donnera rien de plus et vous venez d'économiser de la puissance de traitement!

De MS Docs :

Vous utilisez une instruction return return pour renvoyer chaque élément un par un. Vous consommez une méthode itérateur en utilisant une instruction foreach ou une requête LINQ. Chaque itération de la boucle foreach appelle la méthode itérateur. Lorsqu'une instruction return return est atteinte dans la méthode itérateur, l'expression est renvoyée et l'emplacement actuel dans le code est conservé. L'exécution est redémarrée à partir de cet emplacement lors du prochain appel de la fonction itérateur. Vous pouvez utiliser une instruction de rupture de rendement pour terminer l'itération.

Remarque - Si vous .ToList() sur le résultat d'une méthode qui donne, cela fonctionnera comme si vous renvoyiez une seule liste, ce qui irait à l'encontre du but du rendement.

1
EpicKip