Dans la question Comment puis-je exposer uniquement un fragment d'IList <>, l' une des réponses contenait l'extrait de code suivant:
IEnumerable<object> FilteredList()
{
foreach(object item in FullList)
{
if(IsItemInPartialList(item))
yield return item;
}
}
Que fait le mot clé yield ici? Je l'ai vu référencé à quelques endroits, et une autre question, mais je n'ai pas tout à fait compris ce qu'il fait réellement. J'ai l'habitude de penser au rendement dans le sens où un fil cède à un autre, mais cela ne semble pas pertinent ici.
Réponses:
Le
yield
mot-clé fait en fait beaucoup ici.La fonction renvoie un objet qui implémente l'
IEnumerable<object>
interface. Si une fonction appelante démarreforeach
sur cet objet, la fonction est appelée à nouveau jusqu'à ce qu'elle "cède". Il s'agit du sucre syntaxique introduit en C # 2.0 . Dans les versions précédentes, vous deviez créer vos propres objetsIEnumerable
etIEnumerator
faire des choses comme ça.La façon la plus simple de comprendre un code comme celui-ci est de saisir un exemple, de définir des points d'arrêt et de voir ce qui se passe. Essayez de parcourir cet exemple:
Lorsque vous parcourez l'exemple, vous trouverez le premier appel à
Integers()
retours1
. Le deuxième appel revient2
et la ligneyield return 1
n'est pas exécutée à nouveau.Voici un exemple concret:
la source
yield break;
lorsque vous ne souhaitez plus renvoyer d'articles.yield
n'est pas un mot-clé. Si c'était le cas, je ne pourrais pas utiliser le rendement comme identifiant comme dansint yield = 500;
'If a calling function starts foreach-ing over this object the function is called again until it "yields"'
. ne me semble pas juste. J'ai toujours pensé au mot-clé c # yield dans le contexte de "la récolte donne une récolte abondante", au lieu de "la voiture cède au piéton".Itération. Il crée une machine d'état "sous les couvertures" qui se souvient où vous étiez à chaque cycle supplémentaire de la fonction et reprend à partir de là.
la source
Le rendement a deux grandes utilisations,
Il permet de fournir une itération personnalisée sans créer de collections temporaires.
Cela aide à effectuer une itération avec état.
Afin d'expliquer ci-dessus deux points de manière plus démonstrative, j'ai créé une vidéo simple que vous pouvez regarder ici
la source
yield
. @ Article de projet de code de ShivprasadKoirala Quelle est l'utilisation de C # Yield? de la même explication est également une bonne sourceyield
est un moyen "rapide" de créer un IEnumerator personnalisé (plutôt qu'une classe implémente l'interface IEnumerator).Récemment, Raymond Chen a également publié une intéressante série d'articles sur le mot-clé yield.
Bien qu'il soit nominalement utilisé pour implémenter facilement un modèle d'itérateur, mais peut être généralisé dans une machine à états. Inutile de citer Raymond, la dernière partie contient également des liens vers d'autres utilisations (mais l'exemple du blog d'Entin est particulièrement bon, montrant comment écrire du code sécurisé asynchrone).
la source
À première vue, return return est un sucre .NET pour renvoyer un IEnumerable .
Sans rendement, tous les articles de la collection sont créés à la fois:
Même code en utilisant yield, il retourne article par article:
L'avantage de l'utilisation de yield est que si la fonction consommant vos données a simplement besoin du premier élément de la collection, les autres éléments ne seront pas créés.
L'opérateur de rendement permet la création d'articles selon les besoins. C'est une bonne raison de l'utiliser.
la source
yield return
est utilisé avec les énumérateurs. À chaque appel de déclaration de rendement, le contrôle est retourné à l'appelant mais il garantit que l'état de l'appelé est maintenu. Pour cette raison, lorsque l'appelant énumère l'élément suivant, il continue l'exécution dans la méthode appelée de l'instruction immédiatement après l'yield
instruction.Essayons de comprendre cela avec un exemple. Dans cet exemple, correspondant à chaque ligne, j'ai mentionné l'ordre dans lequel s'exécute l'exécution.
De plus, l'état est maintenu pour chaque énumération. Supposons que j'ai un autre appel à la
Fibs()
méthode, puis l'état sera réinitialisé pour cela.la source
Intuitivement, le mot-clé renvoie une valeur de la fonction sans la quitter, c'est-à-dire que dans votre exemple de code, il renvoie la
item
valeur actuelle , puis reprend la boucle. Plus formellement, il est utilisé par le compilateur pour générer du code pour un itérateur . Les itérateurs sont des fonctions qui renvoient desIEnumerable
objets. Le MSDN a plusieurs articles à leur sujet.la source
Une implémentation de liste ou de tableau charge immédiatement tous les éléments tandis que l'implémentation de yield fournit une solution d'exécution différée.
Dans la pratique, il est souvent souhaitable d'effectuer le minimum de travail selon les besoins afin de réduire la consommation de ressources d'une application.
Par exemple, nous pouvons avoir une application qui traite des millions d'enregistrements à partir d'une base de données. Les avantages suivants peuvent être obtenus lorsque nous utilisons IEnumerable dans un modèle basé sur l'extraction à exécution différée:
Voici une comparaison entre construire une collection en premier comme une liste par rapport à l'utilisation de yield.
Exemple de liste
Sortie de la console
ContactListStore: création de contact 1
ContactListStore: création de contact 2
ContactListStore: création de contact 3
Prêt à parcourir la collection.
Remarque: La collection entière a été chargée en mémoire sans même demander un seul élément dans la liste
Exemple de rendement
Sortie console
Prêt à parcourir la collection.
Remarque: La collection n'a pas été exécutée du tout. Cela est dû à la nature «d'exécution différée» de IEnumerable. La construction d'un élément ne se produira que lorsqu'il est vraiment nécessaire.
Appelons à nouveau la collection et évitons le comportement lorsque nous récupérons le premier contact de la collection.
Sortie console
Prêt à parcourir la collection
ContactYieldStore: création d'un contact 1
Bonjour Bob
Agréable! Seul le premier contact a été établi lorsque le client a "retiré" l'article de la collection.
la source
Voici un moyen simple de comprendre le concept: L'idée de base est que si vous voulez une collection sur laquelle vous pouvez utiliser "
foreach
", mais rassembler les éléments dans la collection coûte cher pour une raison quelconque (comme les interroger dans une base de données), ET vous n'aurez souvent pas besoin de l'intégralité de la collection, puis vous créez une fonction qui crée la collection un élément à la fois et la renvoie au consommateur (qui peut alors mettre fin à l'effort de collecte plus tôt).Pensez-y de cette façon: vous allez au comptoir de viande et vous voulez acheter une livre de jambon tranché. Le boucher prend un jambon de 10 livres à l'arrière, le met sur la machine à trancher, tranche le tout, puis vous ramène le tas de tranches et en mesure une livre. (VIEILLE façon). Avec
yield
, le boucher amène la machine à trancher au comptoir et commence à trancher et à "céder" chaque tranche sur la balance jusqu'à ce qu'elle mesure 1 livre, puis l'enveloppe pour vous et vous avez terminé. L'Ancienne Voie peut être meilleure pour le boucher (lui permet d'organiser ses machines comme il le souhaite), mais la Nouvelle Voie est clairement plus efficace dans la plupart des cas pour le consommateur.la source
Le
yield
mot-clé vous permet de créer unIEnumerable<T>
dans le formulaire sur un bloc itérateur . Ce bloc itérateur prend en charge l' exécution différée et si vous n'êtes pas familier avec le concept, il peut sembler presque magique. Cependant, à la fin de la journée, c'est juste du code qui s'exécute sans astuces étranges.Un bloc itérateur peut être décrit comme du sucre syntaxique où le compilateur génère une machine d'état qui garde une trace de la progression de l'énumération de l'énumérable. Pour énumérer un énumérable, vous utilisez souvent une
foreach
boucle. Cependant, uneforeach
boucle est également du sucre syntaxique. Vous êtes donc deux abstractions retirées du vrai code, c'est pourquoi il peut être difficile au début de comprendre comment tout cela fonctionne ensemble.Supposons que vous ayez un bloc d'itérateur très simple:
Les vrais blocs d'itérateur ont souvent des conditions et des boucles, mais lorsque vous vérifiez les conditions et déroulez les boucles, ils finissent toujours comme des
yield
instructions entrelacées avec un autre code.Pour énumérer le bloc itérateur, une
foreach
boucle est utilisée:Voici la sortie (pas de surprise ici):
Comme indiqué ci
foreach
- dessus est le sucre syntaxique:Pour tenter de démêler cela, j'ai créé un diagramme de séquence avec les abstractions supprimées:
La machine à états générée par le compilateur implémente également l'énumérateur, mais pour rendre le diagramme plus clair, je les ai présentées comme des instances distinctes. (Lorsque la machine d'état est énumérée à partir d'un autre thread, vous obtenez en fait des instances distinctes mais ce détail n'est pas important ici.)
Chaque fois que vous appelez votre bloc itérateur, une nouvelle instance de la machine d'état est créée. Cependant, aucun de votre code dans le bloc itérateur n'est exécuté jusqu'à ce qu'il
enumerator.MoveNext()
s'exécute pour la première fois. Voici comment fonctionne l'exécution différée. Voici un exemple (plutôt idiot):À ce stade, l'itérateur n'a pas exécuté. La
Where
clause crée un nouveauIEnumerable<T>
qui enveloppe leIEnumerable<T>
retourné parIteratorBlock
mais cet énumérable n'a pas encore été énuméré. Cela se produit lorsque vous exécutez uneforeach
boucle:Si vous énumérez l'énumérable deux fois, une nouvelle instance de la machine d'état est créée à chaque fois et votre bloc itérateur exécutera le même code deux fois.
Notez que les méthodes de LINQ aiment
ToList()
,ToArray()
,First()
,Count()
etc. utilisera uneforeach
boucle pour énumérer les dénombrable. Par exempleToList()
, énumérera tous les éléments de l'énumérable et les stockera dans une liste. Vous pouvez maintenant accéder à la liste pour obtenir tous les éléments de l'énumérable sans que le bloc d'itérateur ne s'exécute à nouveau. Il existe un compromis entre l'utilisation du processeur pour produire les éléments de l'énumération plusieurs fois et la mémoire pour stocker les éléments de l'énumération pour y accéder plusieurs fois lors de l'utilisation de méthodes telles queToList()
.la source
Si je comprends bien, voici comment je formulerais cela du point de vue de la fonction implémentant IEnumerable avec yield.
la source
Le mot clé C # yield, pour le dire simplement, permet de nombreux appels à un corps de code, appelé itérateur, qui sait comment retourner avant qu'il ne soit terminé et, lorsqu'il est rappelé, continue là où il s'était arrêté - c'est-à-dire qu'il aide un itérateur deviennent transparents avec état pour chaque élément dans une séquence que l'itérateur renvoie par appels successifs.
En JavaScript, le même concept est appelé Générateurs.
la source
C'est un moyen très simple et facile de créer un énumérable pour votre objet. Le compilateur crée une classe qui encapsule votre méthode et qui implémente, dans ce cas, IEnumerable <object>. Sans le mot-clé yield, vous devez créer un objet qui implémente IEnumerable <object>.
la source
Il produit une séquence énumérable. Ce qu'il fait est en fait de créer une séquence IEnumerable locale et de la renvoyer comme résultat de méthode
la source
Ce lien a un exemple simple
Des exemples encore plus simples sont ici
Notez que return return ne reviendra pas de la méthode. Vous pouvez même mettre
WriteLine
après layield return
Ce qui précède produit un IEnumerable de 4 pouces 4,4,4,4
Ici avec un
WriteLine
. Ajoutera 4 à la liste, imprimera abc, puis ajoutera 4 à la liste, puis terminera la méthode et reviendra donc vraiment de la méthode (une fois la méthode terminée, comme ce serait le cas avec une procédure sans retour). Mais cela aurait une valeur, uneIEnumerable
liste deint
s, qu'il retourne à la fin.Notez également que lorsque vous utilisez yield, ce que vous retournez n'est pas du même type que la fonction. C'est du type d'un élément dans la
IEnumerable
liste.Vous utilisez yield avec le type de retour de la méthode as
IEnumerable
. Si le type de retour de la méthode estint
ouList<int>
et que vous utilisezyield
, alors il ne sera pas compilé. Vous pouvez utiliser leIEnumerable
type de retour de méthode sans rendement, mais il semble que vous ne puissiez pas utiliser le rendement sansIEnumerable
type de retour de méthode.Et pour l'exécuter, vous devez l'appeler d'une manière spéciale.
la source
public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { yield return t; }
etpublic static IEnumerable<TResult> testYieldc<TResult>(TResult t) { return new List<TResult>(); }
yield return
bien (à part la simple chose que j'ai mentionnée), et que je ne l'ai pas beaucoup utilisé et que je ne sais pas grand-chose sur ses utilisations, je ne pense pas que cela devrait être celui qui est accepté.Un point majeur sur le mot-clé Yield est l' exécution paresseuse . Maintenant, ce que je veux dire par exécution paresseuse est d'exécuter en cas de besoin. Une meilleure façon de le dire est de donner un exemple
Exemple: ne pas utiliser Yield, c'est-à-dire pas d'exécution paresseuse.
Exemple: utilisation de Yield ie Lazy Execution.
Maintenant, quand j'appelle les deux méthodes.
vous remarquerez que listItems contiendra 5 éléments (passez votre souris sur listItems pendant le débogage). Alors que yieldItems aura juste une référence à la méthode et non aux articles. Cela signifie qu'il n'a pas exécuté le processus d'obtention d'éléments dans la méthode. Un moyen très efficace d'obtenir des données uniquement en cas de besoin. La mise en œuvre réelle du rendement peut être vue dans ORM comme Entity Framework et NHibernate etc.
la source
Il essaie d'apporter de la bonté Ruby :)
Concept: Ceci est un exemple de code Ruby qui imprime chaque élément du tableau
Chaque implémentation de méthode du tableau donne le contrôle à l'appelant (le «met x») avec chaque élément du tableau soigneusement présenté comme x. L'appelant peut alors faire tout ce qu'il doit faire avec x.
Cependant .Net ne va pas jusqu'au bout ici. C # semble avoir couplé le rendement avec IEnumerable, d'une manière qui vous oblige à écrire une boucle foreach dans l'appelant comme vu dans la réponse de Mendelt. Un peu moins élégant.
la source
yield
est couplé àIEnumerable
, et C # n'a pas le concept Ruby d'un "bloc". Mais C # a des lambdas, ce qui pourrait permettre l'implémentation d'uneForEach
méthode, tout comme Rubyeach
. Cela ne signifie pas pour autant que ce serait une bonne idée de le faire .