Pourquoi devrais - je utiliser foreach au lieu de for (int i=0; i

il semble que la façon cool de boucler en C# et Java est d'utiliser foreach au lieu de C style pour les boucles.

y a-t-il une raison pour laquelle je préférerais ce style plutôt que le style C?

je suis particulièrement intéressé par ces deux cas, mais s'il vous plaît adresser autant de cas que vous avez besoin d'expliquer vos points.

  • je souhaite effectuer une opération sur chaque élément dans une liste.
  • je cherche un élément d'une liste, et souhaite sortir lorsque cet élément est trouvé.
45
demandé sur MedicineMan 2010-12-08 03:57:31

18 réponses

deux raisons majeures auxquelles je peux penser sont:

1) elle s'écarte du type de conteneur sous-jacent. Cela signifie, par exemple, que vous n'avez pas à changer le code qui boucle tous les articles dans le conteneur lorsque vous changez le conteneur -- vous spécifiez le objectif de "faire ceci pour chaque article dans le conteneur", pas le signifie .

2) Il élimine la possibilité de tout-en-un erreur.

en termes de réalisation d'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 au lecteur, par opposition à faire manuellement des choses avec des itérateurs. Idem pour la recherche d'éléments.

71
répondu Stuart Golodetz 2010-12-08 01:11:40

Imaginez que vous êtes le chef d'un restaurant, et que vous préparez une énorme omelette pour un buffet. Tu donnes une cartouche d'une douzaine d'œufs à chacun des deux employés de la cuisine, et tu leur dis de se mettre à craquer, littéralement.

le premier met en place un bol, ouvre la caisse, attrape chaque oeuf tour à tour - de gauche à droite à travers la rangée du haut, puis la rangée du bas - le briser contre le côté de la cuvette et puis le vider dans la cuvette. Il finit par manquer de oeuf. Un travail bien fait.

le second met en place un bol, ouvre la caisse, puis s'élance pour prendre un morceau de papier et un stylo. Il écrit les nombres 0 à 11 à côté des compartiments de la boîte à œufs, et le nombre 0 sur le papier. Il regarde le nombre sur le papier, trouve le compartiment étiqueté 0, supprime l'œuf et les fissures dans le bol. Il regarde le 0 sur le papier encore une fois, pense "0 + 1 = 1", franchit le 0 et écrit 1 sur le papier. Il s'empare de l'œuf du compartiment 1 et le fend. Et ainsi de suite, jusqu'à ce que le numéro 12 soit sur le papier et qu'il sache (sans regarder!) qu'il n'y a pas plus d'oeufs. Un travail bien fait.

on croirait que le deuxième type était un peu défoncé à la tête, non?

Le point de travailler dans un langage de haut niveau est pour éviter d'avoir à décrire les choses dans un ordinateur, et être capable de décrire, dans vos propres termes. Plus le niveau de la langue est élevé, 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 listes liées ne peuvent pas être traitées efficacement en incrémentant un compteur et en indexant: "indexage" signifie commencer à compter dès le début. En C, nous pouvons traiter une liste liée que nous avons nous-mêmes créée en utilisant un pointeur pour la boucle "counter" et en la déréférenciant. Nous pouvons le faire en C++ moderne (et une extension en C# et Java) en utilisant des "itérateurs", mais cela souffre encore du problème de caractère indirect.


enfin, certaines langues ont un niveau assez élevé pour que l'idée de écrire une boucle pour "effectuer une opération sur chaque article d'une liste" ou "rechercher un article d'une liste" soit épouvantable (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 craquer.) Les fonctions qui mettent en place cette structure de boucle sont fournies, et vous leur dites - via une fonction d'ordre supérieur, ou peut-être une valeur de comparaison, dans le cas de la 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.)

77
répondu Karl Knechtel 2010-12-08 01:13:59
  • 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 supportent pas 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 d'itérer sur une collection retournée par un méthode sans variable temporaire supplémentaire:

    foreach(var thingy in SomeMethodCall(arguments)) { ... }
    
37
répondu SLaks 2010-12-08 03:15:53

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: De cette manière, 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;
}

compiler échoue, bien sûr, donc 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 sera détecté que par des erreurs d'exécution. Et si tu n'écris pas de tests... et c'est un rare morceau de code - qui va mordre quelqu'un d'autre - gravement.

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
répondu peter.murray.rust 2010-12-08 08:39:38

foreach fonctionnera de façon identique à un for dans tous les scénarios[1], y compris les scénarios simples tels que ceux que vous décrivez.

toutefois, foreach présente certains avantages non liés à la performance par rapport à for :

  1. Commodité . Vous n'avez pas besoin de garder un i local supplémentaire autour (qui n'a pas d'autre but dans la vie que de faciliter la boucle), et vous n'avez pas besoin de récupérez vous-même la valeur courante dans une variable; la construction de la boucle a déjà pris soin de cela.
  2. Cohérence . Avec foreach , vous pouvez itérer sur des séquences qui ne sont pas des tableaux avec la même facilité. Si vous voulez utiliser for pour boucler une séquence ordonnée sans tableau (par exemple une carte/un dictionnaire), alors vous devez écrire le code un peu différemment. foreach est le même dans tous les cas qu'il couvre.
  3. Sécurité . Avec un grand pouvoir vient une grande responsabilité. Pourquoi ouvrir des opportunités pour les bogues liés à l'incrémentation de la variable loop si vous n'en avez pas besoin en premier lieu?

comme nous le voyons, foreach est "meilleur" à utiliser dans la plupart 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 il y a une raison spécifique réelle pour qu'il s'agisse d'un tableau), la fonctionnalité accrue que le plus bas-le-métal for offre sera la voie à suivre.

[1]" dans tous les scénarios "signifie vraiment" tous les scénarios où la collection est facile à itérer", 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 inamicale d'itérations devrait être conçu, cependant.

16
répondu Jon 2010-12-08 01:26:54

vous devriez probablement considérer aussi LINQ si vous ciblez C# comme une langue, 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 tout simplement faire quelque chose avec l'élément (par exemple, l'imprimer, l'accumuler, le modifier, etc.)? Je soupçonne que c'est ce dernier, puisque foreach en C# ne vous permettra pas de modifier la collection que vous êtes en boucle, ou à les moins pas d'une manière très pratique...

voici deux constructions simples, d'abord en utilisant for et ensuite en utilisant foreach , qui visitent toutes les chaînes d'une liste et les transforment en chaînes en 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 finale i < list.Count au lieu de i < length avec une certaine constante de précalcul length est considérée comme une bonne pratique dans .NET, puisque le compilateur devrait de toute façon vérifier la limite supérieure quand list[i] est invoqué dans la boucle; si ma compréhension est correcte, le compilateur est capable dans certaines circonstances d'optimiser l'élimination de la vérification limite supérieure qu'il aurait normalement fait).

voici l'équivalent foreach :

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

Note: fondamentalement, la construction foreach peut itérer sur n'importe quel IEnumerable ou IEnumerable<T> dans C#, pas juste sur des tableaux ou des listes. Le nombre d'éléments dans la collection pourrait donc ne pas être connu à l'avance, ou pourrait même être infini (dans ce cas, vous devrez certainement inclure une condition de terminaison dans votre boucle, ou il ne sortira pas).

voici quelques solutions équivalentes que je peux imaginer, exprimées en utilisant C # LINQ (et qui introduit le concept d'une expression lambda , essentiellement une fonction en ligne prenant un 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 peuplée par son constructeur:

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

ou le même en utilisant la fonction ToList :

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

Ou encore de même 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 comme un IEnumerable<string> (une collection énumérable de cordes), vous pouvez laisser tomber le ToList :

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

ou peut-être un autre avec les mots clés 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, la recherche d'un élément dans une liste, et souhaitent sortir lorsque cet élément est trouvé peut également être très commodément mis en œuvre en utilisant 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 LINQ simple:

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 sur demande , ce qui signifie qu'effectivement, la liste ne sera itérée que jusqu'à ce que la condition soit remplie, en invoquant la méthode FirstOrDefault .

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

10
répondu Pierre Arnaud 2010-12-09 04:34:54

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

si vous utilisez seulement i comme un indice, par opposition à l'utilisation de la valeur de i pour un autre but 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 être en mesure de juste conduit à 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 l'ignorance sélective"; si vous ne le faites pas besoin de connaître les détails de la façon dont vous itérez une certaine collection, puis trouver un moyen d'ignorer ces détails, et vous écrirez le code plus robuste.

6
répondu tpdi 2010-12-08 01:12:55

raisons d'utiliser foreach:

  • il empêche les erreurs de s'infiltrer (par exemple, vous avez oublié i++ dans la boucle for) qui pourraient causer un dysfonctionnement de la boucle. Il y a beaucoup de façons de bousiller pour les boucles, mais pas beaucoup de façons de bousiller les boucles foreach.
  • il semble beaucoup plus propre / moins cryptique.
  • Un for boucle 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> ).

raisons d'utiliser for :

  • ces types de boucles ont un léger avantage de performance lors de l'itération sur des listes plates (tableaux) parce qu'il n'y a pas de niveau supplémentaire d'indirec tion créée en utilisant un recenseur. (Toutefois, ce gain de rendement est minime.)
  • l'objet que vous voulez énumérer n'implémente pas IEnumerable<T> -- foreach fonctionne seulement sur énumérables.
  • autres situations spécialisées; par exemple, si vous copiez d'un tableau à un autre, foreach ne vous donnera pas une variable d'index que vous pouvez utiliser pour adresser la fente de tableau de destination. for est la seule chose qui fait sens dans de tels cas.

les deux cas que vous énumérez dans votre question sont effectivement identiques lorsque vous utilisez l'une ou l'autre boucle -- dans le premier, vous juste itérer tout le chemin à la fin de la liste, et dans le deuxième vous break; une fois que vous avez trouvé l'article que vous cherchez.

juste pour expliquer foreach plus loin, cette boucle:

IEnumerable<Something> bar = ...;

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

est du sucre syntactique 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
répondu cdhowie 2010-12-08 01:11:12

si vous itérez sur une collection qui met en œuvre IEnumerable, il est plus naturel d'utiliser foreach parce que le membre suivant dans l'itération est assigné en même temps que le test pour atteindre la fin est fait. 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 pour dans votre propre implémentation de IEnumerable. N'ont tout simplement votre Mise en œuvre GetEnumerator() utilisez le C# yield mot-clé dans le corps de votre boucle:

yield return my_array[i];
3
répondu Buggieboy 2010-12-08 01:17:50

Java a les deux types de boucle que vous avez indiqué. Vous pouvez utiliser de la boucle de variantes en fonction de votre besoin. Votre besoin peut être comme ceci

  1. vous souhaitez relire 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 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
répondu Vijay Shanker Dubey 2010-12-08 01:10:31

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

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

1
répondu LukeH 2010-12-08 01:57:48

une raison de ne pas utiliser foreach au moins en java est qu'il va créer un objet iterator qui sera éventuellement ordures collectées. Ainsi, si vous essayez d'écrire du code qui évite la collecte des ordures, il est préférable d'éviter foreach. Cependant, je crois que c'est ok pour les tableaux purs parce qu'il ne crée pas un itérateur.

1
répondu John Schroder 2010-12-08 04:07:01

je pouvais penser de plusieurs raisons

  1. vous can't mess up indexes , aussi dans l'environnement mobile vous n'avez pas d'optimisations de compilateur et lousily écrit pour boucle pourrait faire plusieurs vérifications de bounderay, où comme pour chaque boucle ne fait que 1.
  2. vous can't change data input size (Ajouter / Supprimer des éléments) tout en itérant. Votre code ne freine pas aussi facilement. Si vous avez besoin de filtrer ou transformer des données, puis utiliser d'autres boucles.
  3. vous pouvez itérer sur les structures de données, qui ne peuvent pas être des accès par index, mais peuvent être rampé sur. Pour chacun il suffit d'implémenter iterable interface (java) ou d'étendre IEnumerable (c#).
  4. vous pouvez avoir plus petit boiler plate , par exemple lors de l'analyse XML il est différence entre SAX et StAX, d'abord besoin de copie en mémoire du DOM pour se référer à un élément ce dernier itère juste sur les données (il n'est pas aussi rapide, mais il est efficace de mémoire)

notez que si vous recherchez un élément dans la liste avec pour chacun, vous le faites probablement à tort. Envisagez d'utiliser hashmap ou bimap pour sauter la recherche tous ensemble.

en supposant que le programmeur veut utiliser for loop comme for each en utilisant des itérateurs, il existe un bug commun d'éléments de saut. Ainsi, 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
répondu Margus 2010-12-08 11:12:00

en parlant de code propre, une déclaration foreach est beaucoup plus rapide à lire qu'une déclaration for!

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

0
répondu 2010-12-08 07:53:39

il semble que la plupart des articles sont couverts... voici quelques notes que je ne vois pas mentionnés concernant vos questions spécifiques. Ce sont des règles dures par opposition aux préférences de style d'une manière ou d'une autre:

je souhaite effectuer une opération sur chaque élément dans une liste

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

Il est également intéressant de noter que la façon "fraîche" est maintenant d'utiliser LINQ; il ya beaucoup de ressources que vous pouvez rechercher si vous êtes intéressé.

0
répondu John Rasch 2010-12-08 15:29:46

foreach est Ordre de grandeur plus lent pour la collecte lourde de mise en œuvre.

j'en 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 eu les résultats suivants

Temps = 11320.73 milli secondes (pour la boucle)

Temps = 11742.3296 milli secondes (boucle foreach)

0
répondu sam 2011-11-20 14:36:04

A foreach vous avertit également si la collection que vous énumérez par des changements (c.-à-d. que vous aviez 7 articles dans votre collection...jusqu'à ce qu'une autre opération sur un fil séparé en enlève un et maintenant vous n'avez que 6 @_@)

-1
répondu bitxwise 2010-12-09 01:49:07

voulait juste ajouter que quiconque pense que foreach se traduit dans pour et n'a donc aucune différence de performance est complètement faux. Il y a beaucoup de choses qui se passent sous le capot, c.-à-d. l'énumération du type d'objet qui n'est pas dans un simple pour boucle. Il ressemble plus à une boucle itératrice:

Iterator iter = o.getIterator();
while (iter.hasNext()){
     obj value = iter.next();
     ...do something
}

qui est significativement différent d'un simple pour boucle. Si vous ne comprenez pas Pourquoi, cherchez vtables. De plus, qui sait ce qui est dans les fonction hasNext? Pour autant que nous sachions, cela pourrait être:

while (!haveiwastedtheprogramstimeenough){
}
now advance

exception faite de L'exigence, il y a fonction de mise en œuvre inconnue et l'efficacité étant appelée. Puisque les compilateurs n'optimisent pas les limites de la fonction accross, il n'y a pas d'optimisation ici, juste votre simple recherche vtable et appel de fonction. Ce n'est pas seulement la théorie, dans la pratique, j'ai vu des vitesses significatives en passant de foreach à for sur le C# ArrayList standard. Pour être juste, il était une arraylist avec environ 30 000 objets, mais quand même.

-2
répondu chacham15 2010-12-12 12:12:45