Dans la question Comment puis-je exposer uniquement un fragment d’IList , l’ une des réponses contenait l’extrait de code suivant:
IEnumerable FilteredList() { foreach( object item in FullList ) { if( IsItemInPartialList( item ) yield return item; } }
Qu’est-ce que le mot-clé de rendement fait là? Je l’ai vu faire référence dans quelques endroits, et une autre question, mais je n’ai pas bien compris ce qu’il fait réellement. J’ai l’habitude de penser au rendement au sens où un fil cède à un autre, mais cela ne semble pas pertinent ici.
Le mot-clé de rendement fait vraiment beaucoup ici. La fonction renvoie un object qui implémente l’interface IEnumerable. Si une fonction appelante commence à intervenir sur cet object, la fonction est appelée à nouveau jusqu’à ce qu’elle “cède”. C’est le sucre syntaxique introduit dans C # 2.0. Dans les versions précédentes, vous deviez créer vos propres objects IEnumerable et IEnumerator pour faire ce genre de choses.
La manière la plus simple de comprendre un code comme celui-ci consiste à saisir un exemple, à définir des points d’arrêt et à voir ce qui se passe.
Essayez de passer à travers ceci par exemple:
public void Consumer() { foreach(int i in Integers()) { Console.WriteLine(i.ToSsortingng()); } } public IEnumerable Integers() { yield return 1; yield return 2; yield return 4; yield return 8; yield return 16; yield return 16777216; }
Lorsque vous parcourez l’exemple, vous trouverez le premier appel à Integers () qui renvoie 1. Le second appel renvoie 2 et la ligne “return return 1” n’est plus exécutée.
Voici un exemple concret
public IEnumerable Read (ssortingng sql, Func make, params object[] parms) { using (var connection = CreateConnection()) { using (var command = CreateCommand(CommandType.Text, sql, connection, parms)) { command.CommandTimeout = dataBaseSettings.ReadCommandTimeout; using (var reader = command.ExecuteReader()) { while (reader.Read()) { yield return make(reader); } } } } }
Itération. Il crée une machine d’état “sous les couvertures” qui se souvient de l’endroit où vous vous trouviez sur chaque cycle supplémentaire de la fonction et qui s’en charge.
Le rendement a deux grands usages,
Il permet de fournir une itération personnalisée sans créer de collections temporaires.
Cela aide à faire une itération avec état.
Afin d’expliquer plus de deux points de façon plus démonstrative, j’ai créé une simple vidéo que vous pouvez regarder ici
Récemment, Raymond Chen a également publié une série d’articles intéressants sur le mot-clé rendement.
Bien qu’il soit utilisé nominalement pour implémenter facilement un modèle d’iterator, 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 montre bien comment écrire du code asynchrone).
À première vue, le rendement est un sucre .NET pour renvoyer un IEnumerable.
Sans rendement, tous les articles de la collection sont créés en même temps:
class SomeData { public SomeData() { } static public IEnumerable CreateSomeDatas() { return new List { new SomeData(), new SomeData(), new SomeData() }; } }
Même code utilisant le rendement, il retourne article par article:
class SomeData { public SomeData() { } static public IEnumerable CreateSomeDatas() { yield return new SomeData(); yield return new SomeData(); yield return new SomeData(); } }
L’avantage d’utiliser rendement est que si la fonction consommant vos données nécessite simplement le premier élément de la collection, le rest des éléments ne sera pas créé.
L’opérateur de rendement permet la création d’articles à la demande. C’est une bonne raison de l’utiliser.
yield return
est utilisé avec les enquêteurs. A chaque appel de déclaration de rendement, le contrôle est renvoyé à l’appelant mais il garantit que l’état de l’appelé est maintenu. De ce fait, lorsque l’appelant énumère l’élément suivant, il continue l’exécution dans la méthode appelée depuis l’instruction immédiatement après l’instruction de yield
.
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.
static void Main(ssortingng[] args) { foreach (int fib in Fibs(6))//1, 5 { Console.WriteLine(fib + " ");//4, 10 } } static IEnumerable Fibs(int fibCount) { for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2 { yield return prevFib;//3, 9 int newFib = prevFib + currFib;//6 prevFib = currFib;//7 currFib = newFib;//8 } }
En outre, l'état est maintenu pour chaque énumération. Supposons que j'ai un autre appel à la méthode Fibs()
, alors l'état sera réinitialisé pour cela.
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 valeur de l’ item
cours, puis reprend la boucle. Plus formellement, il est utilisé par le compilateur pour générer du code pour un iterator . Les iterators sont des fonctions qui renvoient des objects IEnumerable
. Le MSDN contient plusieurs articles à leur sujet.
Une implémentation de liste ou de tableau charge tous les éléments immédiatement, tandis que l’implémentation de rendement fournit une solution d’exécution différée.
En pratique, il est souvent souhaitable d’effectuer le minimum de travail nécessaire pour 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 firebase database. Les avantages suivants peuvent être obtenus lorsque nous utilisons IEnumerable dans un modèle basé sur l’extraction avec exécution différée:
Voici une comparaison entre construire une collection en premier comme une liste par rapport à utiliser le rendement.
Exemple de liste
public class ContactListStore : IStore { public IEnumerable GetEnumerator() { var contacts = new List (); Console.WriteLine("ContactListStore: Creating contact 1"); contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" }); Console.WriteLine("ContactListStore: Creating contact 2"); contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" }); Console.WriteLine("ContactListStore: Creating contact 3"); contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" }); return contacts; } } static void Main(ssortingng[] args) { var store = new ContactListStore(); var contacts = store.GetEnumerator(); Console.WriteLine("Ready to iterate through the collection."); Console.ReadLine(); }
Sortie de console
ContactListStore: Créer un contact 1
ContactListStore: Créer un contact 2
ContactListStore: Création d’un 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
public class ContactYieldStore : IStore { public IEnumerable GetEnumerator() { Console.WriteLine("ContactYieldStore: Creating contact 1"); yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" }; Console.WriteLine("ContactYieldStore: Creating contact 2"); yield return new ContactModel() { FirstName = "Jim", LastName = "Green" }; Console.WriteLine("ContactYieldStore: Creating contact 3"); yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" }; } } static void Main(ssortingng[] args) { var store = new ContactYieldStore(); var contacts = store.GetEnumerator(); Console.WriteLine("Ready to iterate through the collection."); Console.ReadLine(); }
Sortie de console
Prêt à parcourir la collection.
Note: La collection n’a pas été exécutée du tout. Cela est dû à la nature “d’exécution différée” de IEnumerable. Construire un object ne se produira que lorsque cela est vraiment nécessaire.
Appelons à nouveau la collection et inversons le comportement lorsque nous récupérons le premier contact de la collection.
static void Main(ssortingng[] args) { var store = new ContactYieldStore(); var contacts = store.GetEnumerator(); Console.WriteLine("Ready to iterate through the collection"); Console.WriteLine("Hello {0}", contacts.First().FirstName); Console.ReadLine(); }
Sortie de console
Prêt à parcourir la collection
ContactYieldStore: Création d’un contact 1
Bonjour bob
Agréable! Seul le premier contact a été créé lorsque le client a “retiré” l’élément de la collection.
Voici un moyen simple de comprendre le concept: L’idée de base est la suivante: si vous voulez une collection sur laquelle vous pouvez utiliser ” foreach
“, mais que collecter les éléments dans la collection est coûteux pour une raison quelconque (comme les extraire d’une firebase database) Et souvent vous n’avez pas besoin de la collection entière, puis vous créez une fonction qui construit la collection un élément à la fois et la renvoie au consommateur (qui peut alors terminer l’effort de collecte au début).
Pensez-y de cette façon: vous allez au comptoir de la viande et vous voulez acheter une livre de jambon en tranches. Le boucher prend un jambon de 10 livres à l’arrière, le place sur la trancheuse, coupe le tout en morceaux, ramène ensuite le tas de tranches et en mesure une livre. (Vieille façon). Avec le yield
, le boucher apporte la trancheuse au comptoir et commence à trancher et à «céder» chaque tranche sur la balance jusqu’à ce qu’elle mesure 1 livre, puis l’enroule pour vous et vous avez terminé. The Old Way est peut-être meilleur pour le boucher (lui permet d’organiser ses machines comme il l’aime), mais la New Way est clairement plus efficace dans la plupart des cas pour le consommateur.
Le mot-clé yield
vous permet de créer un IEnumerable
dans le formulaire sur un bloc iterator . Ce bloc iterator prend en charge l’ exécution différée et si vous ne connaissez pas le concept, il peut sembler presque magique. Cependant, au bout du compte, ce n’est que du code qui s’exécute sans astuces étranges.
Un bloc d’iterators peut être décrit comme un sucre syntaxique où le compilateur génère une machine d’état qui garde une trace de l’avancement de l’énumération de l’énumérateur. Pour énumérer un énumérable, vous utilisez souvent une boucle foreach
. Cependant, une boucle foreach
est également du sucre syntaxique. Donc, vous êtes deux abstractions du vrai code, ce qui explique pourquoi il peut être difficile de comprendre comment tout fonctionne ensemble.
Supposons que vous ayez un bloc d’iterator très simple:
IEnumerable IteratorBlock() { Console.WriteLine("Begin"); yield return 1; Console.WriteLine("After 1"); yield return 2; Console.WriteLine("After 2"); yield return 42; Console.WriteLine("End"); }
Les blocs d’iterators réels ont souvent des conditions et des boucles, mais lorsque vous vérifiez les conditions et que vous déroulez les boucles, elles finissent toujours par être yield
déclarations de yield
entrelacées avec un autre code.
Pour énumérer le bloc iterator, une boucle foreach
est utilisée:
foreach (var i in IteratorBlock()) Console.WriteLine(i);
Voici la sortie (pas de sursockets ici):
Commencer 1 Après 1 2 Après 2 42 Fin
Comme indiqué plus haut, foreach
est le sucre syntaxique:
IEnumerator enumerator = null; try { enumerator = IteratorBlock().GetEnumerator(); while (enumerator.MoveNext()) { var i = enumerator.Current; Console.WriteLine(i); } } finally { enumerator?.Dispose(); }
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 que le diagramme soit plus clair, je les ai montrés comme des instances distinctes. (Lorsque la machine à états est énumérée à partir d’un autre thread, vous obtenez réellement des instances séparées, mais ce détail n’est pas important ici.)
Chaque fois que vous appelez votre bloc iterator, une nouvelle instance de la machine d’état est créée. Cependant, aucun de vos codes dans le bloc iterator n’est exécuté tant que enumerator.MoveNext()
s’exécute pas pour la première fois. C’est comment l’exécution différée fonctionne. Voici un exemple (plutôt stupide):
var evenNumbers = IteratorBlock().Where(i => i%2 == 0);
À ce stade, l’iterator n’a pas été exécuté. La clause Where
crée un nouveau IEnumerable
qui encapsule le IEnumerable
renvoyé par IteratorBlock
mais cet énumérable doit encore être énuméré. Cela se produit lorsque vous exécutez une boucle foreach
:
foreach (var evenNumber in evenNumbers) Console.WriteLine(eventNumber);
Si vous énumérez deux fois l’énumérable, une nouvelle instance de la machine d’état est créée à chaque fois et votre bloc iterator exécutera le même code deux fois.
Notez que les méthodes LINQ telles que ToList()
, ToArray()
, First()
, Count()
etc. utiliseront une boucle foreach
pour énumérer les énumérables. Par exemple, ToList()
énumère tous les éléments de l’énumérable et les stocke 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ération ne s’exécute à nouveau. Il y a un compromis entre l’utilisation de CPU pour produire les éléments de l’énumérable 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 que ToList()
.
Le mot-clé C # yield, pour le dire simplement, permet de nombreux appels à un corpus de code, appelé iterator, qui sait comment revenir avant qu’il soit terminé et, lorsqu’il est appelé à nouveau, continue là où il s’était arrêté. deviennent transparents pour chaque élément dans une séquence que l’iterator renvoie dans les appels successifs.
En JavaScript, le même concept s’appelle Generators.
C’est un moyen très simple et facile de créer un énumérable pour votre object. Le compilateur crée une classe qui encapsule votre méthode et qui implémente, dans ce cas, IEnumerable
Il produit une séquence énumérable. Qu’est-ce qu’il fait est en train de créer une séquence IEnumerable locale et de la retourner comme résultat de méthode
Ce lien a un exemple simple
Des exemples encore plus simples sont ici
public static IEnumerable testYieldb() { for(int i=0;i<3;i++) yield return 4; }
Notez que le retour de rendement ne reviendra pas de la méthode. Vous pouvez même mettre une WriteLine
après le yield return
Ce qui précède produit un IEnumerable de 4 pouces 4,4,4,4
Ici avec une WriteLine
. Ajoutera 4 à la liste, imprimera abc, puis appenda 4 à la liste, puis complétera la méthode et reviendra donc vraiment de la méthode (une fois la méthode terminée, comme cela se produirait avec une procédure sans retour). Mais cela aurait une valeur, une liste IEnumerable
de int
s, qu’elle retourne à la fin.
public static IEnumerable testYieldb() { yield return 4; console.WriteLine("abc"); yield return 4; }
Notez également que lorsque vous utilisez le rendement, ce que vous retournez n'est pas du même type que la fonction. C'est du type d'un élément de la liste IEnumerable
.
Vous utilisez yield avec le type de retour de la méthode comme IEnumerable
. Si le type de retour de la méthode est int
ou List
et que vous utilisez yield
, il ne sera pas compilé. Vous pouvez utiliser le type de retour de la méthode IEnumerable
sans rendement mais il semble que vous ne puissiez peut-être pas utiliser le rendement sans le type de retour de la méthode IEnumerable
.
Et pour l'exécuter, il faut l'appeler d'une manière spéciale.
static void Main(ssortingng[] args) { testA(); Console.Write("try again. the above won't execute any of the function!\n"); foreach (var x in testA()) { } Console.ReadLine(); } // static List testA() static IEnumerable testA() { Console.WriteLine("asdfa"); yield return 1; Console.WriteLine("asdf"); }
Si je comprends bien, voici comment je formulerais ceci du sharepoint vue de la fonction implémentant IEnumerable avec le rendement.
Il essaie d’apporter du Ruby Goodness 🙂
Concept: Ceci est un exemple de code Ruby qui imprime chaque élément du tableau
rubyArray = [1,2,3,4,5,6,7,8,9,10] rubyArray.each{|x| puts x # do whatever with x }
Chaque implémentation de la méthode Array cède 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. C # semble avoir un rendement couplé avec IEnumerable, ce qui vous oblige à écrire une boucle foreach dans l’appelant, comme dans la réponse de Mendelt. Peu moins élégant
//calling code foreach(int i in obCustomClass.Each()) { Console.WriteLine(i.ToSsortingng()); } // CustomClass implementation private int[] data = {1,2,3,4,5,6,7,8,9,10}; public IEnumerable Each() { for(int iLooper=0; iLooper