Il existe apparemment de nombreuses façons de parcourir une collection. Curieux de savoir s'il y a des différences ou pourquoi vous utiliseriez un moyen plutôt que l'autre.
Premier type:
List<string> someList = <some way to init>
foreach(string s in someList) {
<process the string>
}
L'autre manière:
List<string> someList = <some way to init>
someList.ForEach(delegate(string s) {
<process the string>
});
Je suppose que de prime abord, qu'au lieu du délégué anonyme que j'utilise ci-dessus, vous auriez un délégué réutilisable que vous pourriez spécifier ...
Réponses:
Il existe une distinction importante et utile entre les deux.
Parce que .ForEach utilise une
for
boucle pour itérer la collection, ceci est valide (éditer: avant .net 4.5 - l'implémentation a changé et les deux lancent):alors que
foreach
utilise un énumérateur, donc ce n'est pas valide:tl; dr: NE copiez PAS ce code dans votre application!
Ces exemples ne sont pas des meilleures pratiques, ils servent simplement à démontrer les différences entre
ForEach()
etforeach
.La suppression d'éléments d'une liste dans une
for
boucle peut avoir des effets secondaires. Le plus courant est décrit dans les commentaires sur cette question.En règle générale, si vous souhaitez supprimer plusieurs éléments d'une liste, vous souhaiterez séparer la détermination des éléments à supprimer de la suppression réelle. Il ne garde pas votre code compact, mais il garantit que vous ne manquerez aucun élément.
la source
Nous avons eu du code ici (dans VS2005 et C # 2.0) où les ingénieurs précédents ont fait tout leur possible pour l'utiliser
list.ForEach( delegate(item) { foo;});
au lieu deforeach(item in list) {foo; };
tout le code qu'ils ont écrit. par exemple, un bloc de code pour lire les lignes d'un dataReader.Je ne sais toujours pas exactement pourquoi ils ont fait ça.
Les inconvénients de
list.ForEach()
sont:Il est plus détaillé en C # 2.0. Cependant, à partir de C # 3, vous pouvez utiliser la
=>
syntaxe " " pour créer des expressions joliment concises.C'est moins familier. Les personnes qui doivent maintenir ce code se demanderont pourquoi vous l'avez fait de cette façon. Il m'a fallu un certain temps pour décider qu'il n'y avait aucune raison, sauf peut-être pour rendre l'écrivain intelligent (la qualité du reste du code minait cela). Il était également moins lisible, avec le "
})
" à la fin du bloc de code délégué.Voir aussi le livre de Bill Wagner "Effective C #: 50 Specific Ways to Improve Your C #" où il explique pourquoi foreach est préféré à d'autres boucles comme les boucles for ou while - le point principal est que vous laissez le compilateur décider de la meilleure façon de construire la boucle. Si une future version du compilateur parvient à utiliser une technique plus rapide, vous l'obtiendrez gratuitement en utilisant foreach et la reconstruction, plutôt que de modifier votre code.
une
foreach(item in list)
construction vous permet d'utiliserbreak
oucontinue
si vous devez quitter l'itération ou la boucle. Mais vous ne pouvez pas modifier la liste dans une boucle foreach.Je suis surpris de voir que
list.ForEach
c'est légèrement plus rapide. Mais ce n'est probablement pas une raison valable de l'utiliser partout, ce serait une optimisation prématurée. Si votre application utilise une base de données ou un service Web qui, et non un contrôle de boucle, sera presque toujours là où le temps passe. Et l'avez-vous également comparé à unefor
boucle? Lelist.ForEach
pourrait être plus rapide en raison de l' utilisation qui en interne et unefor
boucle sans l'emballage serait encore plus rapide.Je ne suis pas d'accord pour dire que la
list.ForEach(delegate)
version est "plus fonctionnelle" d'une manière significative. Il transfère une fonction à une fonction, mais il n'y a pas de grande différence dans le résultat ou l'organisation du programme.Je ne pense pas que
foreach(item in list)
"dit exactement comment vous voulez que cela soit fait" - unefor(int 1 = 0; i < count; i++)
boucle fait cela, uneforeach
boucle laisse le choix du contrôle au compilateur.Mon sentiment est, sur un nouveau projet, d'utiliser
foreach(item in list)
pour la plupart des boucles afin de respecter l'usage courant et pour la lisibilité, et delist.Foreach()
ne l' utiliser que pour des blocs courts, lorsque vous pouvez faire quelque chose de plus élégamment ou de manière plus compacte avec l'=>
opérateur C # 3 " ". Dans de tels cas, il se peut qu'il existe déjà une méthode d'extension LINQ plus spécifique queForEach()
. Voyez siWhere()
,Select()
,Any()
,All()
,Max()
ou l' une des nombreuses autres méthodes de LINQ ne l'ont pas déjà ce que vous voulez de la boucle.la source
Pour le plaisir, j'ai sauté List dans le réflecteur et voici le C # résultant:
De même, le MoveNext dans Enumerator qui est utilisé par foreach est le suivant:
Le List.ForEach est beaucoup plus réduit que MoveNext - beaucoup moins de traitement - sera plus probablement JIT en quelque chose d'efficace.
De plus, foreach () allouera un nouvel Enumérateur quoi qu'il arrive. Le GC est votre ami, mais si vous faites la même chose foreach à plusieurs reprises, cela fera plus d'objets jetables, par opposition à la réutilisation du même délégué - MAIS - c'est vraiment un cas marginal. Dans une utilisation typique, vous verrez peu ou pas de différence.
la source
Je connais deux choses obscures qui les rendent différents. Allez moi!
Tout d'abord, il y a le bogue classique de créer un délégué pour chaque élément de la liste. Si vous utilisez le mot-clé foreach, tous vos délégués peuvent finir par faire référence au dernier élément de la liste:
La méthode List.ForEach n'a pas ce problème. L'élément actuel de l'itération est passé par valeur en tant qu'argument au lambda externe, puis le lambda interne capture correctement cet argument dans sa propre fermeture. Problème résolu.
(Malheureusement, je pense que ForEach est un membre de List, plutôt qu'une méthode d'extension, bien qu'il soit facile de le définir vous-même afin que vous ayez cette fonctionnalité sur n'importe quel type énumérable.)
Deuxièmement, l'approche de la méthode ForEach a une limitation. Si vous implémentez IEnumerable à l'aide de yield return, vous ne pouvez pas faire de yield return dans le lambda. Donc, parcourir les éléments d'une collection afin de produire des éléments de retour n'est pas possible avec cette méthode. Vous devrez utiliser le mot clé foreach et contourner le problème de fermeture en faisant manuellement une copie de la valeur de la boucle actuelle à l'intérieur de la boucle.
Plus ici
la source
foreach
est "corrigé" dans C # 5. stackoverflow.com/questions/8898925/…Je suppose que l'
someList.ForEach()
appel pourrait être facilement parallélisé alors que la normaleforeach
n'est pas si facile à exécuter en parallèle. Vous pouvez facilement exécuter plusieurs délégués différents sur différents cœurs, ce qui n'est pas si facile à faire avec un fichier normalforeach
.Juste mes 2 cents
la source
Comme on dit, le diable est dans les détails ...
La plus grande différence entre les deux méthodes d'énumération de collection est que
foreach
porte l'état, alors que ceForEach(x => { })
n'est pas le cas.Mais allons un peu plus loin, car il y a certaines choses dont vous devez être conscient et qui peuvent influencer votre décision, et il y a certaines mises en garde dont vous devez être conscient lors du codage pour l'un ou l'autre cas.
Utilisons
List<T>
dans notre petite expérience pour observer le comportement. Pour cette expérience, j'utilise .NET 4.7.2:Répétons cela avec d'
foreach
abord:Nous pourrions développer cela en:
Avec l'enquêteur en main, en regardant sous les couvertures, nous obtenons:
Deux choses deviennent immédiatement évidentes:
Ceci n'est bien sûr en aucun cas thread-safe. Comme indiqué ci-dessus, changer la collection pendant l'itération est juste un mauvais mojo.
Mais qu'en est-il du problème de l'invalidité de la collection lors de l'itération par des moyens extérieurs à nous qui se moquent de la collection pendant l'itération? Les meilleures pratiques suggèrent de versionner la collection pendant les opérations et l'itération, et de vérifier les versions pour détecter quand la collection sous-jacente change.
C'est là que les choses deviennent vraiment troubles. Selon la documentation Microsoft:
Eh bien, qu'est-ce que cela signifie? À titre d'exemple, ce n'est pas parce que
List<T>
implémente la gestion des exceptions que toutes les collections implémentéesIList<T>
feront de même. Cela semble être une violation flagrante du principe de substitution de Liskov:Un autre problème est que l'énumérateur doit implémenter
IDisposable
- cela signifie une autre source de fuites de mémoire potentielles, non seulement si l'appelant se trompe, mais si l'auteur n'implémente pasDispose
correctement le modèle.Enfin, nous avons un problème à vie ... que se passe-t-il si l'itérateur est valide, mais que la collection sous-jacente a disparu? Nous avons maintenant un aperçu de ce qui était ... lorsque vous séparez la durée de vie d'une collection et de ses itérateurs, vous demandez des ennuis.
Examinons maintenant
ForEach(x => { })
:Cela s'étend à:
Il est important de noter ce qui suit:
for (int index = 0; index < this._size && ... ; ++index) action(this._items[index]);
Ce code n'alloue aucun énumérateur (rien à
Dispose
) et ne s'arrête pas pendant l'itération.Notez que cela effectue également une copie superficielle de la collection sous-jacente, mais la collection est maintenant un instantané dans le temps. Si l'auteur n'implémente pas correctement une vérification pour la collection changeante ou devenant «périmée», l'instantané est toujours valide.
Cela ne vous protège en aucun cas du problème des problèmes de durée de vie ... si la collection sous-jacente disparaît, vous avez maintenant une copie superficielle qui pointe vers ce qui était ... mais au moins vous n'avez pas de
Dispose
problème à traiter les itérateurs orphelins ...Oui, j'ai dit itérateurs ... parfois il est avantageux d'avoir un état. Supposons que vous vouliez conserver quelque chose qui ressemble à un curseur de base de données ... peut-être que plusieurs
foreach
stylesIterator<T>
sont la voie à suivre. Personnellement, je n'aime pas ce style de conception car il y a trop de problèmes à vie et vous comptez sur les bonnes grâces des auteurs des collections sur lesquelles vous vous appuyez (à moins que vous n'écriviez littéralement tout vous-même à partir de zéro).Il y a toujours une troisième option ...
Ce n'est pas sexy, mais il a des dents (excuses à Tom Cruise et au film The Firm )
C'est votre choix, mais maintenant vous le savez et cela peut être un choix éclairé.
la source
Vous pouvez nommer le délégué anonyme :-)
Et vous pouvez écrire le second comme:
Ce que je préfère, et économise beaucoup de frappe.
Comme le dit Joachim, le parallélisme est plus facile à appliquer à la seconde forme.
la source
Dans les coulisses, le délégué anonyme est transformé en une méthode réelle afin que vous puissiez avoir une surcharge avec le deuxième choix si le compilateur n'a pas choisi d'intégrer la fonction. En outre, toutes les variables locales référencées par le corps de l'exemple de délégué anonyme changeraient de nature en raison des astuces du compilateur pour masquer le fait qu'elle soit compilée vers une nouvelle méthode. Plus d'informations ici sur la façon dont C # fait cette magie:
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx
la source
La fonction ForEach est membre de la classe générique List.
J'ai créé l'extension suivante pour reproduire le code interne:
Donc, à la fin, nous utilisons un foreach normal (ou une boucle pour si vous le souhaitez).
D'un autre côté, l'utilisation d'une fonction déléguée est juste une autre façon de définir une fonction, ce code:
est équivalent à:
ou en utilisant des expressions labda:
la source
La portée entière de ForEach (fonction déléguée) est traitée comme une seule ligne de code (appelant la fonction) et vous ne pouvez pas définir de points d'arrêt ou entrer dans le code. Si une exception non gérée se produit, tout le bloc est marqué.
la source
List.ForEach () est considéré comme plus fonctionnel.
List.ForEach()
dit ce que vous voulez faire.foreach(item in list)
dit aussi exactement comment vous voulez que cela soit fait. Cela laisseList.ForEach
libre de changer la mise en œuvre de la partie comment à l'avenir. Par exemple, une future version hypothétique de .Net pourrait toujours fonctionnerList.ForEach
en parallèle, en supposant qu'à ce stade, tout le monde a un certain nombre de cœurs de processeur qui sont généralement inactifs.D'autre part,
foreach (item in list)
vous donne un peu plus de contrôle sur la boucle. Par exemple, vous savez que les éléments seront itérés dans une sorte d'ordre séquentiel, et vous pourriez facilement casser au milieu si un élément remplit certaines conditions.Quelques remarques plus récentes sur cette question sont disponibles ici:
la source
La deuxième façon que vous avez montrée utilise une méthode d'extension pour exécuter la méthode déléguée pour chacun des éléments de la liste.
De cette façon, vous avez un autre appel de délégué (= méthode).
De plus, il est possible d'itérer la liste avec une boucle for .
la source
Une chose dont il faut se méfier est de savoir comment quitter la méthode générique .ForEach - voir cette discussion . Bien que le lien semble dire que cette voie est la plus rapide. Je ne sais pas pourquoi - vous penseriez qu'ils seraient équivalents une fois compilés ...
la source
Il y a un moyen, je l'ai fait dans mon application:
Vous pouvez utiliser le comme article de foreach
la source