Les interfaces IEnumerable et IEnumerator de C # sont utilisées par des collections telles que les tableaux et les listes pour standardiser les méthodes de bouclage sur eux et effectuer des actions, comme le filtrage et la sélection avec des instructions LINQ. Nous allons discuter de leur fonctionnement et de leur utilisation.
Qu'est-ce qu'un énumérable?
Le terme «Enumerable» définit un objet qui est censé être itéré, passant sur chaque élément une fois dans l'ordre. En C #, un Énumérable
est un objet comme un tableau, une liste ou tout autre type de collection qui implémente le IEnumerable
interface. Les énumérables normalisent la boucle sur les collections et permettent l'utilisation de la syntaxe de requête LINQ, ainsi que d'autres méthodes d'extension utiles telles que Liste.Où ()
ou Liste.Sélectionnez ()
.
Tous les IEnumerable
l'interface requiert est une méthode, GetEnumerator ()
, qui renvoie un IEnumérateur
. Alors, que font les recenseurs?
Bien, IEnumérateur
nécessite deux méthodes, DéplacerSuivant ()
et Actuel()
. MoveNext est la méthode utilisée pour parcourir chaque élément, en appliquant tout type de logique d'itérateur personnalisée dans le processus, et Actuel
est une méthode utilisée pour obtenir l'élément actuel après DéplacerSuivant
est fait. Vous vous retrouvez avec une interface qui définit les objets qui peuvent être énumérés et comment gérer cette énumération.
Par exemple, chaque fois que vous écrivez un pour chaque
boucle, vous utilisez des énumérateurs. Vous en fait ne peut pas utilisation pour chaque
sur une collection qui ne met pas en œuvre IEnumerable
. Lorsqu'il est compilé, un pour chaque
boucle comme ceci:
foreach (élément Int dans la liste) { // votre code }
Obtient transformé en une boucle while, qui traite jusqu'à ce que le Enumerable soit à court d'éléments, appelant DéplacerSuivant
à chaque fois et en définissant la variable d'itérateur sur .Actuel
.
IEnumerator enumerator = list.GetEnumerator (); while (list.MoveNext ()) { element = (Int) énumérateur. // votre code }
La plupart du temps, vous voulez avoir à implémenter manuellement tout cela, car les énumérables comme les listes sont conçus pour être utilisés dans des boucles et avec des méthodes LINQ. Mais, si vous souhaitez créer votre propre classe de collection, vous pouvez le faire en implémentant IEnumerable et en renvoyant l'énumérateur de la liste dans laquelle vous stockez les données.
Pour les classes contenant des champs sur lesquels vous souhaitez parcourir, cela peut être un moyen simple de nettoyer le code. Au lieu d'avoir le tableau de données public et itérer sur CustomCollection.dataArray
, vous pouvez simplement répéter CustomCollection
lui-même. Bien sûr, si vous voulez qu'elle se comporte davantage comme une liste appropriée, vous devrez également implémenter d'autres interfaces comme ICollection.
Les requêtes LINQ utilisent une exécution différée
La chose la plus importante à noter à propos de LINQ est que les requêtes ne s'exécutent pas toujours tout de suite. De nombreuses méthodes renvoient des énumérables et utilisent exécution différée. Au lieu de boucler dès que la requête est effectuée, il attend en fait jusqu'à ce que l'énumérable soit utilisé dans la boucle, puis effectue la requête pendant DéplacerSuivant
.
Regardons un exemple. Cette fonction prend une liste de chaînes et imprime chaque élément qui est plus long qu'une certaine longueur. Il utilise Où()
pour filtrer la liste, puis pour chaque
pour imprimer chaque élément.
Cette syntaxe est un peu déroutante, surtout si vous avez l'habitude d'utiliser var
(ce que vous ne devriez certainement pas faire ici), car il peut sembler que vous générez une nouvelle liste avec Où()
puis itérer sur cette liste. Dans le cas d'une boucle foreach, un IEnumerable peut remplacer une liste, mais ce n'est pas une liste: vous ne pouvez ni ajouter ni supprimer des éléments.
C # est cependant intelligent, et au lieu d'itérer deux fois sur la liste (ce qui doublerait le temps d'exécution sans raison et allouerait de la mémoire inutile), il ne fait que placer la logique de filtrage dans le pour chaque
boucle. Ce n'est pas exactement ce vers quoi votre code est traduit, mais c'est une bonne idée de le conceptualiser comme ceci, la condition et le filtrage étant détruits à l'intérieur du foreach.
Sous le capot, il le fait en ajoutant la requête à l'énumérateur et en renvoyant un énumérateur qui utilisera la logique de Où()
en appelant Énumérable.MoveNext ()
. Si vous êtes intéressé, vous pouvez découvrir comment System.Linq.Enumerable
le gère en System.Core
.
Cela peut en fait avoir des effets négatifs si vous ne faites pas attention. Et si, entre le Où()
et l'exécution, la collection a été modifiée? Cela pourrait donner l'impression que les requêtes sont exécutées dans le désordre.
Par exemple, donnons une entrée à cette même fonction, mais plutôt que d'imprimer tout de suite, nous ajouterons un nouveau mot à la liste avant le pour chaque
. À première vue, cela peut sembler filtrer les lettres uniques et afficher "Hello World", mais il renvoie en fait "Hello World Banana", car le filtrage n'est appliqué qu'après DéplacerSuivant
est appelée lors de l'itération réelle du pour chaque
.
C'est particulièrement sournois, car nous ne touchons même pas le imprimer
variable qui est transmise à la boucle foreach. Cependant, si vous ajoutez à la place .Lister()
à la fin de la Où
, qui forcera C # à effectuer une itération et à renvoyer une nouvelle liste. À moins que ce ne soit spécifiquement ce que vous voulez, il est presque toujours préférable d’économiser sur le temps d’exécution et les allocations de tas en utilisant pour chaque
avec un Enumerable.
Que sont les coroutines?
Les coroutines sont des fonctions spéciales qui peuvent interrompre l'exécution et revenir au thread principal. Ils sont couramment utilisés pour exécuter des actions longues qui peuvent prendre un certain temps à se terminer, sans que l'application se bloque en attendant la routine. Par exemple, dans les jeux où le framerate de l'application compte beaucoup, de gros problèmes, même de l'ordre de quelques millisecondes, nuiraient à l'expérience utilisateur.
Les coroutines sont assez étroitement liées aux énumérateurs, car ce ne sont en réalité que des fonctions qui renvoient un énumérateur. Cela définit une méthode d'itération, et vous pouvez l'utiliser. Vous n’avez pas à faire grand-chose; changez simplement le type de retour de la fonction en IEnumerator, et plutôt que revenir
quelque chose, tu peux juste rendement retour
, lequel le définit automatiquement comme un énumérateur sans le faire explicitement.
Ensuite, pour fractionner l'exécution sur différentes images, il vous suffit de yield return null
chaque fois que vous souhaitez interrompre l'exécution et exécuter davantage le thread principal. Ensuite, pour utiliser cet Enumérateur, vous devez appeler la fonction et l'affecter à une variable que vous pouvez appeler DéplacerSuivant ()
à intervalles réguliers.
Si vous utilisez Unity, ils ont un contrôleur de coroutine qui peut gérer le traitement des coroutines pour vous, simplement en les démarrant et en les arrêtant avec leurs méthodes. Cependant, il ne fait qu’appeler DéplacerSuivant ()
une fois par image, en vérifiant s'il peut en traiter plus et en gérant la valeur de retour si elle n'est pas nulle. Vous pouvez le gérer vous-même assez facilement:
Bien sûr, vous voudrez peut-être des fonctionnalités supplémentaires, telles que la production de coroutines enfants que vous devrez ajouter à une pile de traitement et la production d'expressions telles que WaitForSeconds
ou attendre la fin des fonctions asynchrones, qu'il vous suffira de vérifier une fois par image (avant d'appeler DéplacerSuivant
) si l'expression renvoie true, puis poursuivez le traitement. De cette façon, la coroutine s'arrêtera complètement jusqu'à ce qu'elle puisse recommencer à fonctionner.