Il y a apparemment plusieurs façons de parcourir une collection. Curieux de savoir s’il existe des différences ou pourquoi vous utiliseriez une méthode plutôt qu'une autre.
Premier type:
List<string> someList = <some way to init>
foreach(string s in someList) {
<process the string>
}
Autre manière:
List<string> someList = <some way to init>
someList.ForEach(delegate(string s) {
<process the string>
});
Je suppose que, au lieu de cela, 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 ...
Il existe une distinction importante et utile entre les deux.
Parce que .ForEach utilise une boucle for
pour itérer la collection, ceci est valide (edit: antérieur à .net 4.5 - l'implémentation a été modifiée et toutes les deux lancées):
someList.ForEach(x => { if(x.RemoveMe) someList.Remove(x); });
alors que foreach
utilise un énumérateur, ce n'est donc pas valide:
foreach(var item in someList)
if(item.RemoveMe) someList.Remove(item);
tl; dr: NE PAS copier ce code dans votre application!
Ces exemples ne sont pas une bonne pratique, ils servent simplement à démontrer les différences entre ForEach()
et foreach
.
Supprimer des éléments d'une liste au sein d'une boucle for
peut avoir des effets secondaires. Le plus commun est décrit dans les commentaires à cette question.
En règle générale, si vous souhaitez supprimer plusieurs éléments d'une liste, vous souhaitez séparer la détermination des éléments à supprimer de la suppression réelle. Votre code ne reste pas compact, mais il garantit que vous ne manquez aucun élément.
Nous avions un code ici (dans VS2005 et C # 2.0) où les ingénieurs précédents se sont démenés pour utiliser list.ForEach( delegate(item) { foo;});
au lieu de foreach(item in list) {foo; };
pour tout le code qu'ils ont écrit. par exemple. un bloc de code permettant de lire les lignes d'un dataReader.
Je ne sais toujours pas exactement pourquoi ils ont fait cela.
Les inconvénients de list.ForEach()
sont les suivants:
C'est plus bavard en C # 2.0. Cependant, à partir de C # 3, vous pouvez utiliser la syntaxe "=>
" pour créer des expressions juxtaposées.
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 moment pour décider qu'il n'y avait pas de raison, sauf peut-être pour donner à l'auteur une apparence intelligente (la qualité du reste du code minait cela). Il était également moins lisible, avec "})
" à la fin du bloc de code délégué.
Voir aussi le livre de Bill Wagner "Effective C #: 50 Façons spécifiques d'améliorer votre C #" où il explique pourquoi on préfère préférer foreach à d'autres boucles comme les boucles for ou while - le point principal est que vous laissez le compilateur décider du meilleur moyen de construire la boucle. Si une future version du compilateur parvient à utiliser une technique plus rapide, vous l'obtiendrez gratuitement en utilisant foreach et en reconstruisant, plutôt que de modifier votre code.
une construction foreach(item in list)
vous permet d'utiliser break
ou continue
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
est légèrement plus rapide. Mais ce n'est probablement pas une raison valable pour l'utiliser, ce serait une optimisation prématurée. Si votre application utilise une base de données ou un service Web, le temps passe presque toujours sans contrôle de boucle. Et l'avez-vous également comparé à une boucle for
? Le list.ForEach
pourrait être plus rapide grâce à son utilisation interne et une boucle for
sans le wrapper serait encore plus rapide.
Je ne suis pas d'accord sur le fait que la version list.ForEach(delegate)
est "plus fonctionnelle" de manière significative. Cela passe d'une fonction à une fonction, mais il n'y a pas de grande différence dans les résultats ou l'organisation du programme.
Je ne pense pas que foreach(item in list)
"dise exactement ce que vous voulez" - une boucle for(int 1 = 0; i < count; i++)
, une boucle foreach
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'utilisation courante et la lisibilité, et d'utiliser list.Foreach()
uniquement pour les blocs courts, lorsque vous pouvez faire quelque chose de plus élégant. ou de manière compacte avec l’opérateur C # 3 "=>
". Dans de tels cas, il peut déjà y avoir une méthode d’extension LINQ plus spécifique que ForEach()
. Voir si Where()
, Select()
, Any()
, All()
, Max()
ou l'une des nombreuses autres méthodes LINQ ne fait pas déjà ce que vous voulez de la boucle .
Pour le plaisir, j'ai inséré List dans le réflecteur et voici le C # résultant:
public void ForEach(Action<T> action)
{
if (action == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
}
for (int i = 0; i < this._size; i++)
{
action(this._items[i]);
}
}
De même, le MoveNext dans Enumerator utilisé par foreach est le suivant:
public bool MoveNext()
{
if (this.version != this.list._version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
if (this.index < this.list._size)
{
this.current = this.list._items[this.index];
this.index++;
return true;
}
this.index = this.list._size + 1;
this.current = default(T);
return false;
}
Le List.ForEach est beaucoup plus réduit que MoveNext - beaucoup moins de traitement - sera plus susceptible de JIT en quelque chose d'efficacité.
De plus, foreach () affectera un nouvel énumérateur, peu importe le cas. Le GC est votre ami, mais si vous faites la même chose à chaque fois, cela créera plus d’objets jetables, par opposition à la réutilisation du même délégué - MAIS - c’est vraiment un cas marginal. En utilisation typique, vous verrez peu ou pas de différence.
Je connais deux choses obscures qui les différencient. Allez moi!
Tout d'abord, il y a le bogue classique de la création d'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 se référer au dernier élément de la liste:
// A list of actions to execute later
List<Action> actions = new List<Action>();
// Numbers 0 to 9
List<int> numbers = Enumerable.Range(0, 10).ToList();
// Store an action that prints each number (WRONG!)
foreach (int number in numbers)
actions.Add(() => Console.WriteLine(number));
// Run the actions, we actually print 10 copies of "9"
foreach (Action action in actions)
action();
// So try again
actions.Clear();
// Store an action that prints each number (RIGHT!)
numbers.ForEach(number =>
actions.Add(() => Console.WriteLine(number)));
// Run the actions
foreach (Action action in actions)
action();
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 disposiez de cette installation pour tous les types énumérables.)
Deuxièmement, l'approche de la méthode ForEach a une limite. Si vous implémentez IEnumerable à l'aide de rendement, vous ne pouvez pas générer de rendement à l'intérieur du lambda. Il est donc impossible d'utiliser cette méthode pour parcourir les éléments d'une collection afin de générer un retour. Vous devrez utiliser le mot-clé foreach et contourner le problème de fermeture en créant manuellement une copie de la valeur de la boucle en cours dans la boucle.
Je suppose que l’appel someList.ForEach()
pourrait être facilement parallélisé, alors que le foreach
normal n’est pas si facile à exécuter en parallèle. Vous pouvez facilement exécuter plusieurs délégués sur des cœurs différents, ce qui n’est pas si facile à faire avec un foreach
normal.
Seulement mes 2 centimes
Vous pouvez nommer le délégué anonyme :-)
Et vous pouvez écrire le second comme:
someList.ForEach(s => s.ToUpper())
Ce que je préfère, et économise beaucoup de dactylographie.
Comme le dit Joachim, le parallélisme est plus facile à appliquer à la deuxième forme.
Dans les coulisses, le délégué anonyme devient une méthode réelle, ce qui vous permet d’avoir une surcharge avec le second choix si le compilateur n’a pas choisi d’inscrire la fonction en ligne. De plus, toute variable locale référencée par le corps de l'exemple de délégué anonyme changerait de nature à cause des astuces du compilateur pour cacher le fait qu'elle soit compilée pour une nouvelle méthode. Plus d'informations ici sur comment C # fait cette magie:
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx
L'ensemble de l'étendue ForEach (fonction déléguée) est traité comme une seule ligne de code (appelant la fonction) et vous ne pouvez pas définir de points d'arrêt ni entrer dans le code. Si une exception non gérée se produit, le bloc entier est marqué.
List.ForEach () est considéré comme plus fonctionnel.
List.ForEach()
dit ce que vous voulez faire. foreach(item in list)
indique également exactement comment vous le souhaitez. Cela laisse List.ForEach
libre de changer l'implémentation de la partie comment dans le futur. Par exemple, une version future hypothétique de .Net peut toujours exécuter List.ForEach
en parallèle, en supposant qu'à ce stade tout le monde possède un certain nombre de cœurs de processeur qui restent généralement inactifs.
Par contre, 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 un ordre séquentiel, et vous pourriez facilement vous casser au milieu si un élément remplissait certaines conditions.
La fonction ForEach est membre de la classe générique List.
J'ai créé l'extension suivante pour reproduire le code interne:
public static class MyExtension<T>
{
public static void MyForEach(this IEnumerable<T> collection, Action<T> action)
{
foreach (T item in collection)
action.Invoke(item);
}
}
Donc, à la fin, nous utilisons un foreach normal (ou une boucle si vous le souhaitez).
Par ailleurs, l’utilisation d’une fonction déléguée n’est qu’un autre moyen de définir une fonction, ce code:
delegate(string s) {
<process the string>
}
est équivalent à:
private static void myFunction(string s, <other variables...>)
{
<process the string>
}
ou en utilisant des expressions labda:
(s) => <process the string>
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 délégué (= méthode).
De plus, il est possible d'itérer la liste avec une boucle for.
Une chose à se méfier est de savoir comment sortir de la méthode générique .ForEach - voir cette discussion . Bien que le lien semble dire que cette voie est la plus rapide. Vous ne savez pas pourquoi - vous pensez qu'ils seraient équivalents une fois compilés ...