Certains aident à comprendre le «rendement»

Dans ma quête éternelle de sucer moins, j’essaie de comprendre la déclaration de “rendement”, mais je continue à rencontrer la même erreur.

Le corps de [une méthode] ne peut pas être un bloc iterator, car ‘System.Collections.Generic.List ‘ n’est pas un type d’interface iterator.

C’est le code où je suis resté bloqué:

foreach (XElement header in headersXml.Root.Elements()){ yield return (ParseHeader(header)); } 

Qu’est-ce que je fais mal? Est-ce que je ne peux pas utiliser le rendement dans un iterator? Alors à quoi ça sert? Dans cet exemple, il est indiqué que List n’est pas un type d’interface d’iterator. ProductMixHeader est une classe personnalisée, mais j’imagine que List est un type d’interface d’iterator, non?

–Modifier–
Merci pour toutes les réponses rapides.
Je sais que cette question n’est pas si nouvelle et que les mêmes ressources reviennent sans cesse.
En fin de compte, je pensais pouvoir retourner la List tant que type de retour, mais puisque la List n’est pas paresseuse, elle ne le peut pas. Changer mon type de retour en IEnumerable résolu le problème: D

Une question quelque peu connexe (ne vaut pas la peine d’ouvrir un nouveau fil): vaut-il la peine de donner IEnumerable comme type de retour si je suis sûr que 99% des cas que je vais utiliser vont quand même .ToList ()? Quelles seront les implications en termes de performances?

Une méthode utilisant return return doit être déclarée comme retournant l’une des deux interfaces suivantes:

 IEnumerable IEnumerator 

(merci Jon et Marc d’ avoir signalé IEnumerator)

Exemple:

 public IEnumerable YourMethod() { foreach (XElement header in headersXml.Root.Elements()) { yield return (ParseHeader(header)); } } 

Le rendement est un producteur de données paresseux, ne produisant un autre élément que lorsque le premier a été récupéré, alors que renvoyer une liste retournera tout en une fois.

Il existe donc une différence et vous devez déclarer la méthode correctement.

Pour plus d’informations, lisez la réponse de Jon ici , qui contient des liens très utiles.

C’est un sujet délicat. En un mot, c’est un moyen simple de mettre en œuvre IEnumerable et ses amis. Le compilateur vous construit une machine à états, transformant les parameters et les variables locales en variables d’instance dans une nouvelle classe. Des choses compliquées.

J’ai quelques ressources à ce sujet:

  • Chapitre 6 de C # in Depth (téléchargement gratuit à partir de cette page)
  • Itérateurs, blocs d’iterators et pipelines de données (article)
  • Détails d’implémentation du bloc Iterator (article)

“yield” crée un bloc iterator – une classe générée par le compilateur pouvant implémenter IEnumerable[] ou IEnumerator[] . Jon Skeet a une très bonne (et libre discussion) à ce sujet au chapitre 6 de C # in Depth .

Mais fondamentalement, pour utiliser “yield”, votre méthode doit renvoyer un IEnumerable[] ou un IEnumerator[] . Dans ce cas:

 public IEnumerable SomeMethod() { // ... foreach (XElement header in headersXml.Root.Elements()){ yield return (ParseHeader(header)); } } 

List implémente Ienumerable.

Voici un exemple qui pourrait nous éclairer sur ce que vous essayez d’apprendre. J’ai écrit ça environ 6 mois

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace YieldReturnTest { public class PrimeFinder { private Boolean isPrime(int integer) { if (0 == integer) return false; if (3 > integer) return true; for (int i = 2; i < integer; i++) { if (0 == integer % i) return false; } return true; } public IEnumerable FindPrimes() { int i; for (i = 1; i < 2147483647; i++) { if (isPrime(i)) { yield return i; } } } } class Program { static void Main(string[] args) { PrimeFinder primes = new PrimeFinder(); foreach (int i in primes.FindPrimes()) { Console.WriteLine(i); Console.ReadLine(); } Console.ReadLine(); Console.ReadLine(); } } } 

Je recommande fortement d’utiliser Reflector pour voir ce que le yield fait réellement pour vous. Vous pourrez voir le code complet de la classe générée par le compilateur lorsqu’il utilisera le rendement, et j’ai constaté que les gens comprennent le concept beaucoup plus rapidement quand ils peuvent voir le résultat de bas niveau (ainsi, à mi-chemin). niveau je suppose).

Pour comprendre le yield , vous devez savoir quand utiliser IEnumerator et IEnumerable (car vous devez utiliser l’un ou l’autre). Les exemples suivants vous aident à comprendre la différence.

Tout d’abord, jetez un coup d’œil à la classe suivante qui implémente deux méthodes: l’une renvoyant IEnumerator , l’autre renvoyant IEnumerable . Je vais vous montrer qu’il y a une grande différence d’utilisation, bien que le code des deux méthodes soit similaire:

 // 2 iterators, one as IEnumerator, one as IEnumerable public class Iterator { public static IEnumerator IterateOne(Func condition) { for(var i=1; condition(i); i++) { yield return i; } } public static IEnumerable IterateAll(Func condition) { for(var i=1; condition(i); i++) { yield return i; } } } 

Maintenant, si vous utilisez IterateOne vous pouvez effectuer les opérations suivantes:

  // 1. Using IEnumerator allows to get item by item var i=Iterator.IterateOne(x => true); // iterate endless // 1.a) get item by item i.MoveNext(); Console.WriteLine(i.Current); i.MoveNext(); Console.WriteLine(i.Current); // 1.b) loop until 100 int j; while (i.MoveNext() && (j=i.Current)<=100) { Console.WriteLine(j); } 

1.a) impressions:

1
2

1.b) imprime:

3
4
...
100

car il continue à compter juste après que les instructions 1.a) ont été exécutées.

Vous pouvez voir que vous pouvez avancer élément par élément en utilisant MoveNext() .


En revanche, IterateAll vous permet d’utiliser les instructions foreach ainsi que LINQ pour un confort IterateAll :

  // 2. Using IEnumerable makes looping and LINQ easier var k=Iterator.IterateAll(x => x<100); // limit iterator to 100 // 2.a) Use a foreach loop foreach(var x in k){ Console.WriteLine(x); } // loop // 2.b) LINQ: take 101..200 of endless iteration var lst=Iterator.IterateAll(x=>true).Skip(100).Take(100).ToList(); // LINQ: take items foreach(var x in lst){ Console.WriteLine(x); } // output list 

2.a) imprime:

1
2
...
99

2.b) imprime:

101
102
...
200


Remarque: IEnumerator et IEnumerable étant des génériques, ils peuvent être utilisés avec n’importe quel type. Cependant, par souci de simplicité, j’ai utilisé int dans mes exemples pour le type T

Cela signifie que vous pouvez utiliser l'un des types de retour IEnumerator ou IEnumerable (la classe personnalisée que vous avez mentionnée dans votre question).

Le type List aucune de ces interfaces, ce qui explique pourquoi vous ne pouvez pas l'utiliser de cette façon. Mais l' exemple 2.b) montre comment vous pouvez en créer une liste.

À quoi ressemble la méthode que vous utilisez? Je ne pense pas que cela puisse être utilisé en boucle.

Par exemple…

 public IEnumerable GetValues() { foreach(ssortingng value in someArray) { if (value.StartsWith("A")) { yield return value; } } } 

La réponse de @Ian P m’a beaucoup aidé à comprendre le rendement et son utilisation. Un cas d’utilisation (majeur) pour le rendement est dans les boucles “pour chaque” après le mot-clé “dans” pour ne pas renvoyer une liste complète. Au lieu de renvoyer une liste complète en une fois, dans chaque boucle “foreach”, un seul élément (l’élément suivant) est renvoyé. Vous gagnerez donc de la performance avec un rendement dans de tels cas. J’ai réécrit le code de @Ian P pour une meilleure compréhension de ce qui suit:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace YieldReturnTest { public class PrimeFinder { private Boolean isPrime(int integer) { if (0 == integer) return false; if (3 > integer) return true; for (int i = 2; i < integer; i++) { if (0 == integer % i) return false; } return true; } public IEnumerable FindPrimesWithYield() { int i; for (i = 1; i < 2147483647; i++) { if (isPrime(i)) { yield return i; } } } public IEnumerable FindPrimesWithoutYield() { var primes = new List(); int i; for (i = 1; i < 2147483647; i++) { if (isPrime(i)) { primes.Add(i); } } return primes; } } class Program { static void Main(string[] args) { PrimeFinder primes = new PrimeFinder(); Console.WriteLine("Finding primes until 7 with yield...very fast..."); foreach (int i in primes.FindPrimesWithYield()) // FindPrimesWithYield DOES NOT iterate over all integers at once, it returns item by item { if (i > 7) { break; } Console.WriteLine(i); //Console.ReadLine(); } Console.WriteLine("Finding primes until 7 without yield...be patient it will take lonkg time..."); foreach (int i in primes.FindPrimesWithoutYield()) // FindPrimesWithoutYield DOES iterate over all integers at once, it returns the complete list of primes at once { if (i > 7) { break; } Console.WriteLine(i); //Console.ReadLine(); } Console.ReadLine(); Console.ReadLine(); } } }