web-dev-qa-db-fra.com

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

Il semble que la façon cool de boucler en C # et Java consiste à utiliser foreach au lieu du style C pour les boucles.

Y a-t-il une raison pour laquelle je préférerais 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 points.

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

Je peux penser à deux raisons principales:

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

2) Il élimine la possibilité d'erreurs au coup par coup.

En termes d'exécution d'une opération sur chaque élément d'une liste, il est intuitif de simplement dire:

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

Il exprime parfaitement l'intention du lecteur, par opposition à faire manuellement des choses avec des itérateurs. Idem pour la recherche d'articles.

71
Stuart Golodetz

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

Le premier met en place un bol, ouvre la caisse, attrape chaque œuf à son tour - de gauche à droite à travers la rangée du haut, puis la rangée du bas - le cassant contre le côté du bol et le vidant ensuite dans le bol. Finalement, il manque d'œufs. Un travail bien fait.

Le second met en place un bol, ouvre la caisse, puis se précipite pour obtenir un morceau de papier et un stylo. Il écrit les chiffres de 0 à 11 à côté des compartiments du carton d'oeufs et le numéro 0 sur le papier. Il regarde le numéro sur le papier, trouve le compartiment étiqueté 0, enlève 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 attrape l'oeuf du 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'oeufs. Un travail bien fait.

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

Le but de travailler dans un langage de haut niveau est d'éviter d'avoir à décrire les choses en termes informatiques et de pouvoir les décrire dans ses propres termes. Plus la langue est élevée, plus c'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 liée ne peuvent pas être traitées efficacement en incrémentant un compteur et en l'indexant: "indexer" signifie recommencer le comptage depuis le début. En C, nous pouvons traiter une liste chaînée que nous avons faite nous-mêmes en utilisant un pointeur pour le "compteur" de boucle et en le déréférençant. Nous pouvons le faire en C++ moderne (et dans une certaine mesure en C # et Java) en utilisant des "itérateurs", mais cela souffre toujours du problème de l'indirectité.


Enfin, certaines langues ont un niveau suffisamment élevé pour que l'idée de réellement écrire une boucle "effectuer une opération sur chaque élément d'une liste" ou "rechercher un élément dans une liste" soit épouvantable (dans de la même manière que le chef ne devrait pas avoir à dire au premier membre du personnel de cuisine comment s'assurer que tous les œufs sont fêlés). Des fonctions sont fournies pour configurer 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 de 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.)

78
Karl Knechtel
  • foreach est plus simple et plus lisible

  • Il peut être plus efficace pour les constructions comme les listes chaînées

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

  • foreach vous permet de parcourir une collection retournée par une méthode sans variable temporaire supplémentaire:

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

Un avantage pour moi est qu'il est moins facile de faire des erreurs comme

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

MISE À JOUR: C'est une façon dont le bogue se produit. Je fais une somme

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

puis décidez de l'agréger davantage. J'enroule donc 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, nous modifions donc manuellement

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 c'est un morceau de code rare - cela mordra mal quelqu'un d'autre -.

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.

19
peter.murray.rust

foreach fonctionnera de manière identique à un for dans tous les scénarios [1], y compris les plus simples comme ceux que vous décrivez.

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

  1. Commodité. Vous n'avez pas besoin de garder un i local supplémentaire autour (qui n'a d'autre but dans la vie que de faciliter la boucle), et vous n'avez pas besoin de récupérer la valeur actuelle dans une variable vous-même; la construction de boucle s'est déjà occupée de cela.
  2. Cohérence. Avec foreach, vous pouvez parcourir les séquences qui ne sont pas des tableaux avec la même facilité. Si vous souhaitez utiliser for pour parcourir une séquence ordonnée non tableau (par exemple une carte/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é. Un grand pouvoir implique de grandes responsabilités. Pourquoi ouvrir des opportunités de bugs liés à l'incrémentation de la variable de boucle si vous n'en avez pas besoin en premier lieu?

Donc, comme nous le voyons, foreach est "mieux" à utiliser dans la plupart des situations .

Cela dit, si vous avez besoin de la valeur de i à d'autres fins, ou si vous manipulez une structure de données que vous savez être un tableau (et qu'il existe une raison spécifique précise pour qu'il s'agisse d'un tableau), l'augmentation les fonctionnalités que les offres les plus simples for seront la voie à suivre.

[1] "Dans tous les scénarios" signifie vraiment "tous les scénarios où la collection est conviviale pour être itérée", 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 hostile aux itérations devrait cependant être conçu.

16
Jon

Vous devriez probablement considérer également LINQ si vous ciblez C # comme langage, car c'est une autre façon logique de faire des boucles.

Par effectuer une opération sur chaque élément d'une liste voulez-vous dire le modifier en place dans la liste, ou simplement faire quelque chose avec l'élément (par exemple l'imprimer, l'accumuler, le modifier, etc.)? Je soupçonne que c'est le dernier, car foreach en C # ne vous permettra pas de modifier la collection que vous parcourez, ou du moins pas de manière pratique ...

Voici deux constructions simples, utilisant d'abord for puis foreach, qui visitent toutes les chaînes d'une liste et les transforment en chaînes majuscules:

List<string> list = ...;
List<string> uppercase = new List<string> ();
for (int i = 0; i < list.Count; i++)
{
    string name = list[i];
    uppercase.Add (name.ToUpper ());
}

(notez que l'utilisation de la condition de fin i < list.Count au lieu de i < length avec une constante de pré-ordinateur length est considérée comme une bonne pratique dans .NET, car le compilateur devra de toute façon vérifier la borne supérieure lorsque list[i] est invoqué dans la boucle; si ma compréhension est correcte, le compilateur est en mesure, dans certaines circonstances, d'optimiser la vérification de limite supérieure qu'il aurait normalement effectuée).

Voici l'équivalent foreach:

List<string> list = ...;
List<string> uppercase = new List<string> ();
foreach (name in list)
{
    uppercase.Add (name.ToUpper ());
}

Remarque: fondamentalement, la construction foreach peut itérer sur n'importe quel IEnumerable ou IEnumerable<T> En C #, pas seulement sur des tableaux ou des listes. Le nombre d'éléments dans la collection peut donc ne pas être connu à l'avance, ou peut-être même infini (auquel cas vous devrez certainement inclure une condition de terminaison dans votre boucle, sinon elle ne se fermera pas).

Voici quelques solutions équivalentes auxquelles je peux penser, exprimées en utilisant C # LINQ (et qui introduisent le concept d'un expression lambda, essentiellement une fonction en ligne prenant une fonction x et retournant x.ToUpper () dans les exemples suivants):

List<string> list = ...;
List<string> uppercase = new List<string> ();
uppercase.AddRange (list.Select (x => x.ToUpper ()));

Ou avec la liste uppercase renseignée par son constructeur:

List<string> list = ...;
List<string> uppercase = new List<string> (list.Select (x => x.ToUpper ()));

Ou la même chose en utilisant la fonction ToList:

List<string> list = ...;
List<string> uppercase = list.Select (x => x.ToUpper ()).ToList ();

Ou toujours la même chose avec l'inférence de type:

List<string> list = ...;
var uppercase = list.Select (x => x.ToUpper ()).ToList ();

ou si cela ne vous dérange pas d'obtenir le résultat sous la forme d'un IEnumerable<string> (une collection énumérable de chaînes), vous pouvez supprimer le ToList:

List<string> list = ...;
var uppercase = list.Select (x => x.ToUpper ());

Ou peut-être un autre avec les mots clés de type C # SQL from et select, qui est entièrement équivalent:

List<string> list = ...;
var uppercase = from name in list
                select name => name.ToUpper ();

LINQ est très expressif et très souvent, je pense que le code est plus lisible qu'une simple boucle.

Votre deuxième question, recherche d'un élément dans une liste, et souhaitez quitter lorsque cet élément est trouvé peut également être très facilement implémentée à l'aide de LINQ. Voici un exemple de boucle foreach:

List<string> list = ...;
string result = null;
foreach (name in list)
{
    if (name.Contains ("Pierre"))
    {
        result = name;
        break;
    }
}

Voici l'équivalent simple de LINQ:

List<string> list = ...;
string result = list.Where (x => x.Contains ("Pierre")).FirstOrDefault ();

ou avec la syntaxe de requête:

List<string> list = ...;

var results = from name in list
              where name.Contains ("Pierre")
              select name;
string result = results.FirstOrDefault ();

L'énumération results n'est exécutée que à la demande, ce qui signifie que la liste ne sera itérée que jusqu'à ce que la condition soit remplie, lors de l'invocation de la méthode FirstOrDefault dessus .

J'espère que cela apporte un peu plus de contexte au débat for ou foreach, au moins dans le monde .NET.

10
Pierre Arnaud

Comme l'a répondu Stuart Golodetz, c'est une abstraction.

Si vous utilisez uniquement i comme index, au lieu d'utiliser la valeur de i à d'autres fins comme

   String[] lines = getLines();
   for( int i = 0 ; i < 10 ; ++i ) {
      System.out.println( "line " + i + lines[i] ) ;
   }

alors il n'y a pas besoin de connaître la valeur actuelle de i, et pouvoir simplement conduire à la possibilité d'erreurs:

   Line[] pages = getPages();
   for( int i = 0 ; i < 10 ; ++i ) {
        for( int j = 0 ; j < 10 ; ++i ) 
             System.out.println( "page " + i + "line " + j + page[i].getLines()[j];
   }

Comme le dit Andrew Koenig, "l'abstraction est une ignorance sélective"; Si vous n'avez pas besoin de connaître les détails de la façon dont vous itérez une collection, trouvez un moyen d'ignorer ces détails et vous écrirez un code plus robuste.

6
tpdi

Raisons d'utiliser foreach:

  • Il empêche les erreurs de s'infiltrer (par exemple, vous avez oublié de i++ dans la boucle for) qui pourrait entraîner un dysfonctionnement de la boucle. Il existe de nombreuses façons de visser pour les boucles, mais pas beaucoup de façons de visser pour chaque boucle.
  • Il semble beaucoup plus propre/moins cryptique.
  • Une boucle for peut même ne pas être possible dans certains cas (par exemple, si vous avez un IEnumerable<T>, qui ne peut pas être indexé comme un IList<T> pouvez).

Raisons d'utiliser for:

  • Ces types de boucles présentent un léger avantage en termes de performances lors de l'itération sur des listes plates (tableaux) car aucun niveau d'indirection supplémentaire n'est créé à l'aide d'un énumérateur. (Cependant, ce gain de performances est minime.)
  • L'objet que vous souhaitez énumérer n'implémente pas IEnumerable<T> - foreach ne fonctionne que sur les énumérateurs.
  • Autres situations spécialisées; par exemple, si vous copiez d'un tableau vers un autre, foreach ne vous donnera pas de variable d'index que vous pouvez utiliser pour adresser l'emplacement de tableau de destination. for est à peu près la seule chose qui a du sens dans de tels cas.

Les deux cas que vous listez dans votre question sont effectivement identiques lorsque vous utilisez l'une ou l'autre boucle - dans le premier, vous répétez simplement jusqu'à la fin de la liste, et dans le second, vous break; une fois que vous avez trouvé l'article que vous recherchez.

Juste pour expliquer foreach plus loin, cette boucle:

IEnumerable<Something> bar = ...;

foreach (var foo in bar) {
    // do stuff
}

est du sucre syntaxique pour:

IEnumerable<Something> bar = ...;

IEnumerator<Something> e = bar.GetEnumerator();
try {
    Something foo;
    while (e.MoveNext()) {
        foo = e.Current;
        // do stuff
    }
} finally {
    ((IDisposable)e).Dispose();
}
4
cdhowie

Si vous parcourez une collection qui implémente IEnumerable, il est plus naturel d'utiliser foreach car le membre suivant de l'itération est affecté en même temps que le test pour atteindre la fin est effectué. Par exemple.,

 foreach (string day in week) {/* Do something with the day ... */}

est plus simple que

for (int i = 0; i < week.Length; i++) { day = week[i]; /* Use day ... */ }

Vous pouvez également utiliser une boucle for dans l'implémentation IEnumerable de votre classe. Demandez simplement à votre implémentation GetEnumerator () d'utiliser le mot clé C # yield dans le corps de votre boucle:

yield return my_array[i];
3
Buggieboy

Java possède les deux types de boucles que vous avez indiqués. Vous pouvez utiliser l'une des variantes de boucle for selon vos besoins. Votre besoin peut être comme ça

  1. Vous souhaitez réexécuter l'index de votre élément de recherche dans la liste.
  2. Vous voulez obtenir l'article lui-même.

Dans le premier cas, vous devez utiliser le classique (style c) pour la boucle. mais dans le second cas, vous devez utiliser la boucle foreach.

La boucle foreach peut également être utilisée dans le premier cas. mais dans ce cas, vous devez maintenir votre propre index.

2

Je pourrais penser à plusieurs raisons

  1. tu can't mess up indexes, également dans un environnement mobile, vous n'avez pas d'optimisations du compilateur et écrit en boucle pour mal pourrait faire plusieurs vérifications de limite, où comme pour chaque boucle ne fait que 1.
  2. tu can't change data input size (ajouter/supprimer des éléments) lors de son itération. Votre code ne freine pas aussi facilement. Si vous devez filtrer ou transformer des données, utilisez d'autres boucles.
  3. vous pouvez parcourir les structures de données, qui ne peuvent pas être accessibles par index, mais peuvent être explorées. Pour chacun a juste besoin que vous implémentiez iterable interface (Java) ou étendez IEnumerable (c #).
  4. vous pouvez avoir de plus petits boiler plate, par exemple, lors de l'analyse de XML, c'est la différence entre SAX et StAX, il faut d'abord une copie en mémoire du DOM pour faire référence à un élément qui vient d'itérer sur les données (ce n'est pas aussi rapide, mais il est efficace en mémoire)

Notez que si vous recherchez un élément dans la liste avec pour chacun, vous le faites très probablement mal. Pensez à utiliser hashmap ou bimap pour ignorer la recherche tous ensemble.

En supposant que le programmeur souhaite utiliser for loop comme for each à l'aide d'itérateurs, il existe un bogue courant de saut d'éléments. Donc, dans cette scène, c'est plus sûr.

for ( Iterator<T> elements = input.iterator(); elements.hasNext(); ) {
   // Inside here, nothing stops programmer from calling `element.next();` 
   // more then once.
} 
1
Margus

l'une des raisons de ne pas utiliser foreach au moins dans Java est qu'il créera un objet itérateur qui sera éventuellement récupéré. Par conséquent, si vous essayez d'écrire du code qui évite la récupération de place, il vaut mieux éviter foreach. Cependant, je pense que c'est correct pour les tableaux purs car il ne crée pas d'itérateur.

1
John Schroder

Si vous pouvez faire ce dont vous avez besoin avec foreach alors utilisez-le; sinon - par exemple, si vous avez besoin de la variable d'index elle-même pour une raison quelconque - alors utilisez for. Facile!

(Et vos deux scénarios sont également possibles avec for ou foreach.)

1
LukeH

foreach est d'un ordre de grandeur plus lent pour la mise en œuvre d'une collecte intensive.

J'ai la preuve. Ce sont mes conclusions

J'ai utilisé le profileur simple suivant pour tester leurs performances

static void Main(string[] args)
{
    DateTime start = DateTime.Now;
    List<string> names = new List<string>();
    Enumerable.Range(1, 1000).ToList().ForEach(c => names.Add("Name  = " + c.ToString()));
    for (int i = 0; i < 100; i++)
    {
        //For the for loop. Uncomment the other when you want to profile foreach loop
        //and comment this one
        //for (int j = 0; j < names.Count; j++)
          //  Console.WriteLine(names[j]);
        //for the foreach loop
        foreach (string n in names)
        {
            Console.WriteLine(n);
        }
    }
    DateTime end = DateTime.Now;
    Console.WriteLine("Time taken = " + end.Subtract(start).TotalMilliseconds + " milli seconds");

Et j'ai obtenu les résultats suivants

Temps pris = 11320,73 milli secondes (pour boucle)

Temps pris = 11742,3296 milli secondes (pour chaque boucle)

0
sam

Il semble que la plupart des articles soient couverts ... voici quelques notes supplémentaires que je ne vois pas mentionnées concernant vos questions spécifiques. Ce sont des règles strictes par opposition aux préférences de style dans un sens ou dans l'autre:

Je souhaite effectuer une opération sur chaque élément d'une liste

Dans une boucle foreach, vous ne pouvez pas changer la valeur de la variable d'itération, donc si vous cherchez à changer la valeur d'un élément spécifique dans votre liste, vous devez utiliser for.

Il convient également de noter que la méthode "cool" consiste à utiliser LINQ; il existe de nombreuses ressources que vous pouvez rechercher si vous êtes intéressé.

0
John Rasch

En parlant de code propre, une instruction foreach est beaucoup plus rapide à lire qu'une instruction for!

Linq (en C #) peut faire la même chose, mais les développeurs novices ont tendance à avoir du mal à les lire!

0
user523650