48 votes

Pourquoi devrais-je utiliser foreach au lieu de for (int i=0 ; i<length ; i++) dans les boucles ?

Il semble que la façon la plus cool de faire des boucles en C# et en Java soit d'utiliser des foreach au lieu des boucles for de style C.

Y a-t-il une raison pour laquelle je devrais préférer ce style au style C ?

Je suis particulièrement intéressé par ces deux cas, mais veuillez aborder autant de cas que nécessaire pour expliquer vos arguments.

  • Je souhaite effectuer une opération sur chaque élément d'une liste.
  • Je recherche un élément dans une liste, et je souhaite sortir lorsque cet élément est trouvé.

81voto

Karl Knechtel Points 24349

Imaginez que vous êtes le chef de cuisine d'un restaurant et que vous êtes tous en train de préparer une énorme omelette pour un buffet. Vous remettez un carton contenant une douzaine d'œufs à chacun des deux membres du personnel de cuisine et leur dites de se mettre au travail, littéralement.

Le premier installe un bol, ouvre la caisse, saisit chaque œuf à tour de rôle - de gauche à droite à travers la rangée supérieure, puis la rangée inférieure - en le cassant contre le côté du bol, puis en le vidant dans le bol. Il finit par ne plus avoir d'œufs. Un travail bien fait.

Le second installe un bol, ouvre la caisse et s'empresse d'aller chercher une feuille de papier et un stylo. Il écrit les chiffres 0 à 11 à côté des compartiments de la boîte à œufs, et le chiffre 0 sur le papier. Il regarde le nombre sur le papier, trouve le compartiment marqué 0, prend l'œuf et le casse dans le bol. Il regarde à nouveau le 0 sur le papier, pense "0 + 1 = 1", raye le 0 et écrit 1 sur le papier. Il prend l'œuf dans le compartiment 1 et le casse. Et ainsi de suite, jusqu'à ce que le chiffre 12 soit sur le papier et qu'il sache (sans regarder !) qu'il n'y a plus d'œufs. Un travail bien fait.

On pourrait penser que le deuxième gars était un peu dérangé dans sa tête, non ?

L'intérêt de travailler dans un langage de haut niveau est d'éviter de devoir décrire les choses dans les termes de l'ordinateur et de pouvoir les décrire dans vos propres termes. Plus le langage est de haut niveau, plus cela est vrai. L'incrémentation d'un compteur dans une boucle est une distraction de ce que vous voulez vraiment faire : traiter chaque élément.


De plus, les structures de type liste chaînée ne peuvent pas être traitées efficacement en incrémentant un compteur et en indexant : "Indexer" signifie recommencer à compter depuis le début. En C, nous pouvons traiter une liste chaînée que nous avons créée nous-mêmes en utilisant un pointeur pour le "compteur" de la boucle et en le déréférençant. Nous pouvons faire cela dans le C++ moderne (et dans une certaine mesure dans le C# et le Java) en utilisant des "itérateurs", mais cela souffre toujours du problème de l'indirectité.


Enfin, certains langages sont d'un niveau suffisamment élevé pour que l'idée de réellement écrire une boucle pour "effectuer une opération sur chaque élément d'une liste" ou "rechercher un élément dans une liste" est consternant (de la même manière que le chef cuisinier ne devrait pas avoir à dire au premier membre du personnel de cuisine comment s'assurer que tous les œufs sont cassés). Des fonctions sont fournies pour mettre en place cette structure de boucle, et vous leur dites - via une fonction d'ordre supérieur, ou peut-être une valeur de comparaison, dans le cas d'une recherche - ce qu'il faut faire dans la boucle. (En fait, vous pouvez faire ces choses en C++, bien que les interfaces soient quelque peu maladroites).

75voto

Stuart Golodetz Points 12679

Les deux principales raisons auxquelles je pense sont :

1) Il s'abstrait du type de conteneur sous-jacent. Cela signifie, par exemple, que vous n'avez pas besoin de modifier le code qui boucle sur tous les éléments du conteneur lorsque vous changez de conteneur - vous spécifiez l'option objectif de "faire ceci pour chaque élément du conteneur", et non la signifie .

2) Il élimine la possibilité d'erreurs de type "off-by-one".

Pour ce qui est d'effectuer une opération sur chaque élément d'une liste, il est intuitif de dire simplement :

for(Item item: lst)
{
  op(item);
}

Il exprime parfaitement l'intention du lecteur, contrairement à ce que l'on fait manuellement avec les itérateurs. Idem pour la recherche d'éléments.

38voto

SLaks Points 391154
  • foreach est plus simple et plus lisible

  • Il peut être plus efficace pour des constructions telles que les listes liées.

  • Toutes les collections ne supportent pas l'accès aléatoire ; la seule façon d'itérer une HashSet<T> ou un Dictionary<TKey, TValue>.KeysCollection es foreach .

  • foreach vous permet d'itérer dans une collection renvoyée par une méthode sans variable temporaire supplémentaire :

    foreach(var thingy in SomeMethodCall(arguments)) { ... }

19voto

peter.murray.rust Points 13406

L'un des avantages pour moi est qu'il est moins facile de faire des erreurs telles que

    for(int i = 0; i < maxi; i++) {
        for(int j = 0; j < maxj; i++) {
...
        }
    }

MISE À JOUR : Voici une façon dont le bug se produit. Je fais une somme

int sum = 0;
for(int i = 0; i < maxi; i++) {
    sum += a[i];
}

et ensuite décider de l'agréger davantage. Donc j'enroule la boucle dans une autre.

int total = 0;
for(int i = 0; i < maxi; i++) {
   int sum = 0;
    for(int i = 0; i < maxi; i++) {
        sum += a[i];
    }
    total += sum;
}

La compilation échoue, bien sûr, alors nous éditons à la main

int total = 0;
for(int i = 0; i < maxi; i++) {
   int sum = 0;
    for(int j = 0; j < maxj; i++) {
        sum += a[i];
    }
    total += sum;
}

Il y a maintenant au moins DEUX erreurs dans le code (et plus si nous avons confondu maxi et maxj ) qui ne seront détectées que par des erreurs d'exécution. Et si vous n'écrivez pas de tests... et que c'est un morceau de code rare - cela va mordre quelqu'un d'AUTRE - méchamment.

C'est pourquoi c'est une bonne idée d'extraire la boucle interne dans une méthode :

int total = 0;
for(int i = 0; i < maxi; i++) {
    total += totalTime(maxj);
}

private int totalTime(int maxi) {
   int sum = 0;
    for(int i = 0; i < maxi; i++) {
        sum += a[i];
    }
    return sum;
}

et c'est plus lisible.

16voto

Jon Points 194296

foreach se comportera de la même manière qu'un for dans tous les scénarios[1], y compris les plus simples comme celui que vous décrivez.

Cependant, foreach a certains avantages non liés aux performances par rapport à for :

  1. Commodité . Vous n'avez pas besoin de garder un local supplémentaire i (qui n'a pas d'autre but dans la vie que de faciliter la boucle), et vous n'avez pas besoin d'aller chercher la valeur actuelle dans une variable vous-même ; la construction de la boucle s'en est déjà chargée.
  2. Cohérence . Avec foreach Si l'on veut utiliser les tableaux, on peut itérer sur des séquences qui ne sont pas des tableaux avec la même facilité. Si vous voulez utiliser for pour boucler sur une séquence ordonnée qui n'est pas un tableau (par exemple, une carte ou un dictionnaire), vous devez écrire le code un peu différemment. foreach est le même dans tous les cas qu'il couvre.
  3. Sécurité . De grands pouvoirs impliquent de grandes responsabilités. Pourquoi ouvrir des possibilités de bogues liés à l'incrémentation de la variable de la boucle si vous n'en avez pas besoin en premier lieu ?

Donc, comme nous le voyons, foreach est "meilleure" à utiliser dans le plus situations.

Cela dit, si vous avez besoin de la valeur de i à d'autres fins, ou si vous manipulez une structure de données dont vous savez qu'il s'agit d'un tableau (et qu'il y a une raison spécifique pour laquelle il s'agit d'un tableau), les fonctionnalités accrues que l'option plus terre à terre for sera la meilleure solution.

[1] "Dans tous les scénarios" signifie en réalité "tous les scénarios où la collection est favorable à l'itération", ce qui serait en fait "la plupart des scénarios" (voir les commentaires ci-dessous). Je pense vraiment qu'un scénario d'itération impliquant une collection non favorable à l'itération devrait être conçu, cependant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X