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é.
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.
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.)
-
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 unDictionary<TKey, TValue>.KeysCollection
estforeach
. -
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)) { ... }
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.
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
:
- 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. - 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 utiliserfor
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. - 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.
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.
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.
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 unIEnumerable<T>
qui ne peut pas être indexé comme unIList<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();
}
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];
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
- vous souhaitez relire l'index de votre élément de recherche dans la liste.
- 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.
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
.)
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.
je pouvais penser de plusieurs raisons
- 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. - 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. - 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'étendreIEnumerable
(c#). - 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.
}
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é.
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)
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 @_@)
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.