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).
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.
De msdn:
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();
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.
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.
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.