Quel est le but ou l'avantage d'utiliser des itérateurs de rendement en C#?

tous les exemples que j'ai vus de l'utilisation de yield return x; à l'intérieur d'une méthode C# pourraient être faits de la même manière en retournant simplement la liste entière. Dans ces cas, y a-t-il un avantage ou un avantage à utiliser la syntaxe yield return plutôt que de retourner la liste?

aussi, dans quels types de scénarios yield return serait utilisé que vous ne pouvez pas simplement retourner la liste complète?

71
demandé sur CoderDennis 2009-07-06 22:11:21

10 réponses

mais si vous construisiez vous-même une collection?

en général, les itérateurs peuvent être utilisés pour générer une séquence d'objets . Par exemple, la méthode Enumerable.Range n'a aucun type de collecte interne. Il génère juste le numéro suivant sur demande . Il y a de nombreuses utilisations à cette génération de séquence paresseuse en utilisant une machine d'état. La plupart d'entre eux sont couverts par la programmation fonctionnelle les concepts de .

à mon avis, si vous regardez les itérateurs juste comme un moyen d'énumérer à travers une collection (c'est juste un des cas d'utilisation les plus simples), vous allez dans la mauvaise direction. Comme je l'ai dit, les itérateurs sont des moyens pour retourner des séquences. La séquence pourrait même être infinie . Il n'y aurait aucun moyen de retourner une liste de longueur infinie et d'utiliser les 100 premiers éléments. Il a d'être paresseux parfois. Retourner une collection est considérablement différent de retourner un générateur de collection (qui est ce qu'un itérateur est). C'est comparer des pommes à des oranges.

exemple hypothétique:

static IEnumerable<int> GetPrimeNumbers() {
   for (int num = 2; ; ++num) 
       if (IsPrime(num))
           yield return num;
}

static void Main() { 
   foreach (var i in GetPrimeNumbers()) 
       if (i < 10000)
           Console.WriteLine(i);
       else
           break;
}

cet exemple imprime des nombres premiers inférieurs à 10000. Vous pouvez facilement le modifier pour imprimer des nombres inférieurs à un million sans toucher l'algorithme de génération de nombres premiers. Dans cet exemple, vous ne pouvez pas retourner une liste de tous les nombres premiers parce que la séquence est infini et le consommateur ne sait même pas combien d'articles il veut depuis le début.

109
répondu Mehrdad Afshari 2009-07-06 21:35:16

les bonnes réponses ici suggèrent qu'un avantage de yield return est que vous n'avez pas besoin de créer une liste ; listes peuvent être coûteux. (Aussi, après un certain temps, vous les trouverez volumineux et inélégant.)

mais si vous n'avez pas de liste?

yield return vous permet de parcourir structures de données (pas nécessairement des listes) de plusieurs façons. Par exemple, si votre objet est un arbre, vous pouvez parcourir les noeuds en pré - ou post - ordre sans créer d'autres listes ou modifier la structure de données sous-jacente.

public IEnumerable<T> InOrder()
{
    foreach (T k in kids)
        foreach (T n in k.InOrder())
            yield return n;
    yield return (T) this;
}

public IEnumerable<T> PreOrder()
{
    yield return (T) this;
    foreach (T k in kids)
        foreach (T n in k.PreOrder())
            yield return n;
}
24
répondu Ray 2009-07-06 23:08:58

Évaluation Paresseuse/Exécution Différée

les blocs itérateurs" rendement "n'exécuteront aucun du code jusqu'à ce que vous appeliez réellement pour ce résultat spécifique. Cela signifie qu'ils peuvent aussi être enchaînés efficacement. Pop quiz: si la fonction "ReadLines ()" lit toutes les lignes d'un fichier texte et est implémentée en utilisant un bloc itérateur, combien de fois le code suivant itérera-t-il sur le fichier?

var query = ReadLines(@"C:\MyFile.txt")
                            .Where(l => l.Contains("search text") )
                            .Select(l => int.Parse(l.SubString(5,8))
                            .Where(i => i > 10 );

int sum=0;
foreach (int value in query) 
{
    sum += value;
}

la réponse est exactement une, et ce pas jusqu'à ce que dans la boucle foreach .

séparation des préoccupations

encore une fois en utilisant la fonction hypothétique ReadLines() d'en haut, nous pouvons facilement séparer le code qui lit le fichier du code qui filtre les lignes inutiles du code qui analyse réellement les résultats. Cette première, surtout, est très réutilisable.

Listes Infinies

voir ma réponse à cette question pour un bon exemple:

c # fibonacci fonction returning errors

fondamentalement, j'implémente la séquence de fibonacci en utilisant un bloc itérateur qui ne s'arrêtera jamais (du moins pas avant D'atteindre MaxInt), et puis j'utilise cette implémentation d'une manière sûre.

Sémantique Améliorée

C'est l'une de ces choses qui est beaucoup plus difficile à expliquer avec prose qu'il est à juste qui avec un simple visuel 1 :

Imperative vs Functional Separation of Concerns

si vous ne pouvez pas voir l'image, elle montre deux versions du même code, avec des highlights de fond pour des préoccupations différentes. Le code linq a toutes les couleurs bien groupées, tandis que le code impératif traditionnel a les couleurs entremêlées. L'auteur fait valoir (et je suis d'accord) que ce résultat est typique de l'utilisation de linq vs utilisant impératif code... que linq fasse un meilleur travail d'organisation de votre code pour avoir un meilleur flux entre les sections.


1 je crois que c'est la source originale: https://twitter.com/mariofusco/status/571999216039542784 . Notez aussi que ce code est Java, mais le C# serait similaire.

15
répondu Joel Coehoorn 2017-05-23 11:54:47

parfois, les séquences que vous devez retourner sont tout simplement trop grandes pour rentrer dans la mémoire. Par exemple, il y a environ trois mois, j'ai participé à un projet de migration de données entre les bases de données MS SLQ. Les données ont été exportées en format XML. Yield return s'est avéré très utile avec XmlReader . Cela rendait la programmation plus facile. Par exemple, supposons qu'un fichier ait 1000 client éléments-si vous venez de lire ce fichier dans mémoire, cela exigera de les stocker tous dans la mémoire en même temps, même s'ils sont manipulés séquentiellement. Ainsi, vous pouvez utiliser des itérateurs pour parcourir la collection un par un. Dans ce cas, vous avez passer de la mémoire pour un élément.

comme il s'est avéré, en utilisant XmlReader pour notre projet était le seul moyen de faire fonctionner l'application - il a fonctionné pendant une longue période, mais au moins il n'a pas accroché l'ensemble du système et n'a pas soulevé OutOfMemoryException . Bien sûr, vous pouvez travailler avec XmlReader sans itérateurs de rendement. Mais les itérateurs ont rendu ma vie beaucoup plus facile (je n'écrirais pas le code pour l'importation si rapidement et sans problèmes). Regardez cette page pour voir comment les itérateurs de rendement sont utilisés pour résoudre des problèmes réels (pas seulement scientifiques avec des séquences infinies).

10
répondu SPIRiT_1984 2010-03-24 14:25:31

dans les scénarios jouets/démonstration, il n'y a pas beaucoup de différence. Mais il y a des situations où il est utile de produire des itérateurs - parfois, la liste entière n'est pas disponible (par exemple, les flux), ou la liste est coûteuse sur le plan informatique et peu susceptible d'être nécessaire dans son intégralité.

9
répondu Dan Davies Brackett 2009-07-06 18:13:23

si la liste entière est gigantesque, il pourrait manger beaucoup de mémoire juste pour s'asseoir autour, alors qu'avec le rendement vous jouez seulement avec ce que vous avez besoin, quand vous en avez besoin, indépendamment de combien d'articles il ya.

2
répondu nilamo 2009-07-06 18:17:22

jetez un oeil à cette discussion sur le blog D'Eric White (excellent blog d'ailleurs) sur lazy versus eager evaluation .

2
répondu JP Alioto 2009-07-06 18:17:25

en utilisant le yield return vous pouvez itérer sur les articles sans jamais avoir à construire une liste. Si vous n'avez pas besoin de la liste, mais que vous voulez itérer sur un ensemble d'éléments, il peut être plus facile d'écrire

foreach (var foo in GetSomeFoos()) {
    operate on foo
}

que

foreach (var foo in AllFoos) {
    if (some case where we do want to operate on foo) {
        operate on foo
    } else if (another case) {
        operate on foo
    }
}

vous pouvez mettre toute la logique pour déterminer si oui ou non vous voulez opérer sur foo à l'intérieur de votre méthode en utilisant des rendements et vous foreach boucle peut être beaucoup plus concis.

2
répondu AgileJon 2009-07-06 18:22:14

voici ma réponse acceptée à exactement la même question:

Yield mot-clé valeur ajoutée?

une Autre façon de regarder itérateur méthodes, c'est qu'ils font le travail difficile de transformer un algorithme "à l'intérieur". Envisager un analyseur. Il tire du texte d'un flux, cherche des motifs et génère une description logique de haut niveau du contenu.

maintenant, je peux rendre cela facile pour moi-même comme un analyseur auteur en prenant l'approche SAX, dans laquelle je dispose d'une interface de rappel que je signale chaque fois que je trouve la prochaine pièce du modèle. Donc dans le cas de SAX, chaque fois que je trouve le début d'un élément, j'appelle la méthode beginElement , et ainsi de suite.

Mais cela crée des problèmes pour mes utilisateurs. Ils doivent implémenter l'interface handler et donc ils doivent écrire une classe de machine d'état qui répond aux méthodes de callback. C'est difficile d'être juste, donc la chose la plus facile à faire do est d'utiliser une implémentation de stock qui construit un arbre DOM, et alors ils auront la commodité d'être en mesure de marcher l'arbre. Mais alors toute la structure est tamponnée dans la mémoire - pas bon.

mais pourquoi pas plutôt que j'écrive mon analyseur comme méthode itératrice?

IEnumerable<LanguageElement> Parse(Stream stream)
{
    // imperative code that pulls from the stream and occasionally 
    // does things like:

    yield return new BeginStatement("if");

    // and so on...
}

qui ne sera pas plus difficile à écrire que l'approche callback-interface-il suffit de rendre retour un objet dérivé de ma classe de base LanguageElement au lieu d'appeler un callback méthode.

l'utilisateur peut maintenant utiliser foreach à boucle à travers la sortie de mon analyseur, de sorte qu'ils obtiennent une interface de programmation impérative très pratique.

le résultat est que les deux côtés d'une API personnalisée semblent être en contrôle , et sont donc plus faciles à écrire et à comprendre.

2
répondu Daniel Earwicker 2017-05-23 11:47:24

la raison fondamentale de l'utilisation de yield est qu'il génère/retourne une liste par lui-même. Nous pouvons utiliser la liste retournée pour itérer davantage.

2
répondu Tapas Ranjan Singh 2012-11-23 10:03:29