foreach vs someList.ForEach () {}

Il existe apparemment de nombreuses manières d’itérer une collection. Curieux s’il y a des différences ou pourquoi vous utiliseriez d’une manière ou d’une autre.

Premier type:

List someList =  foreach(ssortingng s in someList) {  } 

Autre moyen:

 List someList =  someList.ForEach(delegate(ssortingng s) {  }); 

Je suppose que du haut de ma tête, 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: avant .net 4.5 – l’implémentation a changé et ils lancent tous les deux):

 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 copiez pas ce code dans votre application!

Ces exemples ne sont pas des meilleures pratiques, ils ne font que démontrer les différences entre ForEach() et foreach .

La suppression d’éléments d’une liste dans 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 devez 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.

Nous avions du code ici (dans VS2005 et C # 2.0) où les ingénieurs précédents se sont mis en list.ForEach( delegate(item) { foo;}); pour utiliser list.ForEach( delegate(item) { foo;}); au lieu de foreach(item in list) {foo; }; foreach(item in list) {foo; }; pour tout le code qu’ils ont écrit. Par exemple, un bloc de code pour lire les lignes d’un lecteur de données.

Je ne sais toujours pas exactement pourquoi ils ont fait ça.

Les inconvénients de list.ForEach() sont:

  • C’est plus bavard en C # 2.0. Cependant, en C # 3 et suivants, vous pouvez utiliser la syntaxe ” => ” pour créer des expressions bien nettes.

  • C’est moins familier. Les personnes devant conserver ce code se demanderont pourquoi vous l’avez fait de cette façon. Il m’a fallu du temps pour décider qu’il n’y avait pas de raison, sauf peut-être pour que l’auteur paraisse intelligent (la qualité du rest du code a nui à cela). C’était aussi moins lisible, avec le ” }) ” à la fin du bloc de code délégué.

  • Voir aussi le livre de Bill Wagner “Efficace C #: 50 moyens spécifiques pour améliorer votre C #” où il parle de la préférence de foreach par rapport à d’autres boucles comme pour ou pendant des boucles – l’essentiel est de laisser le compilateur décider du meilleur la boucle. Si une future version du compilateur réussit à utiliser une technique plus rapide, vous obtiendrez ceci gratuitement en utilisant foreach et rebuilding, plutôt que de modifier votre code.

  • une construction foreach(item in list) vous permet d’utiliser break ou continue si vous avez besoin de quitter l’itération ou la boucle. Mais vous ne pouvez pas modifier la liste dans une boucle foreach.

Je suis surpris de voir cette list.ForEach 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 firebase database ou un service Web qui, pas le contrôle de la boucle, sera presque toujours le moment venu. Et l’avez-vous comparé à une boucle for ? La list.ForEach pourrait être plus rapide en raison de son utilisation interne et une boucle for sans le wrapper serait encore plus rapide.

Je ne suis pas d’accord pour dire que la version list.ForEach(delegate) est “plus fonctionnelle” de manière significative. Il transmet une fonction à une fonction, mais il n’y a pas de grande différence dans le résultat ou dans l’organisation du programme.

Je ne pense pas que foreach(item in list) “dit exactement comment vous le voulez” – une boucle for(int 1 = 0; i < count; i++) fait, une boucle foreach laisse le choix du contrôle jusqu'à le compilateur.

J'ai l'impression, sur un nouveau projet, d'utiliser foreach(item in list) pour la plupart des boucles afin de respecter l'usage courant et la lisibilité, et d'utiliser list.Foreach() uniquement pour les blocs courts. de manière élégante ou compacte avec l'opérateur C # 3 " => ". Dans de tels cas, il existe peut-être déjà 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 sauté la liste dans le réflecteur et ceci est le résultat C #:

 public void ForEach(Action 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 qui est 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 probablement plus efficace.

En outre, foreach () allouera un nouvel énumérateur, peu importe quoi. Le GC est votre ami, mais si vous faites la même chose à plusieurs resockets, cela fera plus d'objects jetables, au lieu de réutiliser le même délégué - MAIS - c'est vraiment un cas marginal. Dans l'usage typique, vous verrez peu ou pas de différence.

Je connais deux choses obscures qui les rendent différentes. Allez moi!

Tout d’abord, il y a le bug classique de créer un délégué pour chaque élément de la liste. Si vous utilisez le mot-clé foreach, tous vos delegates peuvent se référer au dernier élément de la liste:

  // A list of actions to execute later List actions = new List(); // Numbers 0 to 9 List 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 transmis par valeur en tant qu’argument à la lambda externe, puis le lambda interne capture correctement cet argument dans sa propre fermeture. Problème résolu.

(Malheureusement, je crois 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, de sorte que vous avez cette possibilité sur tout type énumérable.)

Deuxièmement, l’approche de la méthode ForEach est limitée. Si vous implémentez IEnumerable en utilisant le rendement, vous ne pouvez pas effectuer de retour sur investissement dans le lambda. Il n’est donc pas possible de parcourir les éléments d’une collection pour générer des résultats. Vous devrez utiliser le mot clé foreach et contourner le problème de fermeture en effectuant manuellement une copie de la valeur de la boucle actuelle dans la boucle.

Plus ici

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 pourriez facilement exécuter plusieurs delegates différents sur des cœurs différents, ce qui n’est pas si facile à faire avec un foreach normal.
Juste 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 frappe.

Comme le dit Joachim, le parallélisme est plus facile à appliquer à la seconde forme.

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 voulez le faire. Cela laisse List.ForEach libre de modifier la mise en œuvre de la partie future dans le futur. Par exemple, une future version hypothétique de .Net pourrait 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 processeurs 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 une sorte d’ordre séquentiel, et vous pourriez facilement casser au milieu si un élément rencontre des conditions.

Dans les coulisses, le délégué anonyme devient une méthode réelle, de sorte que vous pouvez avoir un peu de temps avec le deuxième choix si le compilateur n’a pas choisi d’inclure la fonction. De plus, 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’il soit compilé dans une nouvelle méthode. Plus d’infos ici sur comment C # fait cette magie:

http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx

La deuxième manière que vous avez montrée utilise une méthode d’extension pour exécuter la méthode de délégué 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. Je ne sais pas pourquoi – vous penseriez qu’ils seraient équivalents une fois compilés …

La fonction ForEach est membre de la liste de classes générique.

J’ai créé l’extension suivante pour reproduire le code interne:

 public static class MyExtension { public static void MyForEach(this IEnumerable collection, Action action) { foreach (T item in collection) action.Invoke(item); } } 

Donc, à la fin, nous utilisons un foreach normal (ou une boucle si vous voulez).

D’un autre côté, l’utilisation d’une fonction de délégué est une autre façon de définir une fonction, ce code:

 delegate(ssortingng s) {  } 

est équivalent à:

 private static void myFunction(ssortingng s, ) {  } 

ou en utilisant des expressions labda:

 (s) =>  

L’étendue entière de ForEach (fonction de délégué) est traitée 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é.