Perché dovrei usare foreach anziché for (int i = 0; i <length; i ++) nei loop?

Sembra che il modo migliore per eseguire il ciclo in C # e Java sia utilizzare foreach anziché lo stile C per i loop.

C’è una ragione per cui preferirei questo stile rispetto allo stile C?

Sono particolarmente interessato a questi due casi, ma ti preghiamo di rispondere a tutti i casi che ti occorrono per spiegare i tuoi punti.

  • Desidero eseguire un’operazione su ciascun elemento in un elenco.
  • Sto cercando un elemento in un elenco e desidero uscire quando viene trovato quell’elemento.

I due motivi principali a cui posso pensare sono:

1) Si astrae dal tipo di contenitore sottostante. Ciò significa, ad esempio, che non devi modificare il codice che scorre su tutti gli elementi nel contenitore quando cambi il contenitore: stai specificando l’ objective di “fai questo per ogni object nel contenitore”, non i mezzi .

2) Elimina la possibilità di errori off-one.

In termini di esecuzione di un’operazione su ciascun elemento di un elenco, è intuitivo dire semplicemente:

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

Esprime perfettamente l’intento del lettore, al contrario di fare manualmente le cose con gli iteratori. Idem per la ricerca di oggetti.

Immagina di essere il capo cuoco di un ristorante e stai preparando un’enorme omelette per un buffet. Porti un cartone di una dozzina di uova a ciascuno dei due membri del personale della cucina e dì loro di farsi scrocchiare, letteralmente.

Il primo sistema una ciotola, apre la cassa, afferra ogni uovo a sua volta – da sinistra a destra attraverso la fila superiore, quindi la fila inferiore – rompendola contro il lato della ciotola e poi svuotandola nella ciotola. Alla fine lui finisce le uova. Un lavoro ben fatto.

Il secondo sistema una ciotola, apre la cassa e poi si spegne per prendere un pezzo di carta e una penna. Scrive i numeri da 0 a 11 accanto agli scomparti del cartone delle uova e il numero 0 sulla carta. Guarda il numero sul foglio, trova lo scompartimento etichettato 0, rimuove l’uovo e lo scava nella ciotola. Guarda di nuovo lo 0 sul foglio, pensa “0 + 1 = 1”, taglia lo 0 e scrive 1 sul foglio. Afferra l’uovo dal compartimento 1 e lo spezza. E così via, fino a quando il numero 12 è sulla carta e lui sa (senza guardare!) Che non ci sono più uova. Un lavoro ben fatto.

Penseresti che il secondo ragazzo fosse un po ‘incasinato, vero?

Il punto di lavorare in un linguaggio di alto livello è evitare di dover descrivere le cose nei termini di un computer e di essere in grado di descriverle secondo le proprie condizioni. Più alto è il linguaggio, più questo è vero. Incrementare un contatore in un ciclo è una distrazione da ciò che si vuole veramente fare: elaborare ogni elemento.


Inoltre, le strutture di tipo a lista collegata non possono essere elaborate in modo efficiente incrementando un contatore e indicizzando in: “indicizzazione” significa iniziare con il conteggio dall’inizio. In C, possiamo elaborare una lista concatenata che abbiamo creato usando un puntatore per il “contatore” del ciclo e dereferenziarlo. Possiamo farlo nel C ++ moderno (e in misura maggiore in C # e Java) usando “iteratori”, ma questo soffre ancora del problema di indirettamente.


Infine, alcune lingue hanno un livello abbastanza alto che l’idea di scrivere effettivamente un loop per “eseguire un’operazione su ogni elemento di un elenco” o “cercare un elemento in un elenco” è terribile (nello stesso modo in cui il capo cuoco non dovrei dire al primo membro dello staff di cucina come assicurarsi che tutte le uova siano incrinate). Sono fornite funzioni che impostano la struttura del ciclo e comunicate loro – tramite una funzione di ordine superiore, o forse un valore di confronto, nel caso di ricerca – cosa fare all’interno del ciclo. (In effetti, puoi fare queste cose in C ++, anche se le interfacce sono piuttosto goffe.)

  • foreach è più semplice e più leggibile

  • Può essere più efficiente per costruzioni come le liste collegate

  • Non tutte le raccolte supportano l’accesso casuale; l’unico modo per iterare un HashSet o un Dictionary.KeysCollection è foreach .

  • foreach consente di eseguire iterazioni attraverso una raccolta restituita da un metodo senza una variabile temporanea aggiuntiva:

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

Un vantaggio per me è che è meno facile fare errori come

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

AGGIORNAMENTO: questo è un modo in cui si verifica l'errore. Faccio una sum

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

e quindi decidere di aggregarlo di più. Quindi avvolgo il ciclo in un altro.

 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 compilazione fallisce, ovviamente, quindi modifichiamo manualmente

 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; } 

Ora ci sono almeno DUE errori nel codice (e di più se abbiamo confuso maxi e maxj) che saranno rilevati solo dagli errori di runtime. E se non scrivi test ... ed è un pezzo di codice raro - questo morderà qualcuno ELSE - male.

Ecco perché è una buona idea estrarre il ciclo interno in un metodo:

 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; } 

ed è più leggibile.

foreach si esibirà identicamente in un for in tutti gli scenari [1], compresi quelli semplici come quelli che descrivi.

Tuttavia, foreach ha alcuni vantaggi non correlati alle prestazioni for :

  1. Convenienza . Non è necessario mantenere un i locale extra intorno (che non ha uno scopo nella vita se non quello di facilitare il ciclo) e non è necessario recuperare il valore corrente in una variabile; il costrutto del ciclo si è già occupato di questo.
  2. Coerenza Con foreach , puoi scorrere su sequenze che non sono matrici con la stessa facilità. Se si desidera utilizzare for eseguire il ciclo su una sequenza ordinata non ordinata (ad esempio una mappa / dizionario), è necessario scrivere il codice in modo leggermente diverso. foreach è lo stesso in tutti i casi che copre.
  3. Sicurezza Con un grande potere viene una grande responsabilità. Perché aprire opportunità per bug relativi all’incremento della variabile loop se non ne hai bisogno in primo luogo?

Quindi, come vediamo, foreach è “migliore” da utilizzare nella maggior parte delle situazioni.

Detto questo, se hai bisogno del valore di i per altri scopi, o se stai gestendo una struttura di dati che sai essere un array (e c’è un motivo specifico per cui si tratta di un array), la maggiore funzionalità è tanto più bassa -to-the-metal for offerte sarà la strada da percorrere.

[1] “In tutti gli scenari” significa davvero “tutti gli scenari in cui la raccolta è amichevole per essere ripetuta”, che in realtà sarebbe “la maggior parte degli scenari” (vedi i commenti sotto). Penso davvero che uno scenario di iterazione che coinvolge una collezione iterativa-ostile dovrebbe essere progettato, comunque.

Probabilmente dovresti considerare anche LINQ se hai come target C # come lingua, poiché questo è un altro modo logico per fare loop.

Eseguendo un’operazione su ogni elemento di una lista , intendi modificarlo nella lista o semplicemente fare qualcosa con l’object (ad esempio stamparlo, accumularlo, modificarlo, ecc.)? Sospetto che sia il secondo, dato che foreach in C # non ti permetterà di modificare la collezione su cui stai viaggiando, o almeno non in modo conveniente …

Qui ci sono due semplici costrutti, prima usando for e poi usando foreach , che visita tutte le stringhe in una lista e le trasforma in stringhe maiuscole:

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

(si noti che l'uso della condizione di fine i < list.Count invece di i < length con una costante di length precomputer è considerato una buona pratica in .NET, poiché il compilatore dovrebbe comunque controllare il limite superiore quando viene richiamata la list[i] nel ciclo, se la mia comprensione è corretta, il compilatore è in grado in alcune circostanze di ottimizzare il controllo del limite superiore che avrebbe normalmente eseguito).

Ecco l'equivalente di foreach :

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

Nota: in pratica, il costrutto foreach può scorrere su qualsiasi IEnumerable o IEnumerable in C #, non solo su array o liste. Il numero di elementi nella collezione potrebbe quindi non essere conosciuto in anticipo, o potrebbe anche essere infinito (nel qual caso si dovrebbe certamente includere alcune condizioni di terminazione nel proprio ciclo, altrimenti non uscirà).

Ecco alcune soluzioni equivalenti a cui posso pensare, espresse usando C # LINQ (e che introduce il concetto di un'espressione lambda , fondamentalmente una funzione inline che prende una x e restituisce x.ToUpper () nei seguenti esempi):

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

Oppure con la lista uppercase compilata dal suo costruttore:

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

O lo stesso usando la funzione ToList :

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

O ancora lo stesso con l'inferenza di tipo:

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

o se non ti dispiace di ottenere il risultato come IEnumerable (una collezione enumerabile di stringhe), puoi abbandonare la ToList :

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

O forse un altro con il C # SQL-like from e select parole chiave, che è completamente equivalente:

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

LINQ è molto espressivo e molto spesso, ritengo che il codice sia più leggibile di un semplice loop.

La seconda domanda, la ricerca di un elemento in un elenco e la possibilità di uscire quando viene trovato tale elemento, possono anche essere implementate in modo molto conveniente utilizzando LINQ. Ecco un esempio di un ciclo foreach :

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

Ecco l'equivalente lineare di LINQ:

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

o con la syntax della query:

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

L'enumerazione dei results viene eseguita solo su richiesta , il che significa che, in modo efficace, l'elenco verrà iterato fino a quando la condizione non verrà soddisfatta, quando si richiama il metodo FirstOrDefault su di esso.

Spero che questo porti un po 'più di contesto al dibattito foreach o foreach , almeno nel mondo .NET.

Come rispose Stuart Golodetz, è un’astrazione.

Se stai usando solo i come indice, invece di usare il valore di i per altri scopi come

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

quindi non è necessario conoscere il valore corrente di i , ed essere in grado di portare solo alla possibilità di errori:

  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]; } 

Come dice Andrew Koenig, "L'astrazione è l'ignoranza selettiva"; se non è necessario conoscere i dettagli di come si itera la raccolta, quindi trovare un modo per ignorare quei dettagli e scrivere un codice più robusto.

Motivi per cui utilizzare foreach:

  • Impedisce agli errori di insinuarsi (ad es. Ti sei dimenticato di usare i++ nel ciclo for) che potrebbe causare il malfunzionamento del loop. Ci sono molti modi per rovinare i loop, ma non molti modi per rovinare i loop foreach.
  • Sembra molto più pulito / meno criptico.
  • Un ciclo for potrebbe non essere nemmeno ansible in alcuni casi (ad esempio, se si dispone di un object IEnumerable , che non può essere indicizzato come un IList can).

Ragioni da usare for :

  • Questi tipi di loop hanno un leggero vantaggio in termini di prestazioni durante l’iterazione su liste piatte (array) poiché non esiste un livello aggiuntivo di indirection creato utilizzando un enumeratore. (Tuttavia, questo guadagno di prestazioni è minimo.)
  • L’object che si desidera enumerare non implementa IEnumerableforeach funziona solo su enumerables.
  • Altre situazioni specializzate; ad esempio, se si sta copiando da una matrice a un’altra, foreach non fornisce una variabile di indice che è ansible utilizzare per indirizzare lo slot di matrice di destinazione. for è l’unica cosa che ha senso in questi casi.

I due casi elencati nella domanda sono effettivamente identici quando si utilizza il loop-in, si itera fino alla fine dell’elenco e nel secondo si break; una volta trovato l’articolo che stai cercando.

Solo per spiegare ulteriormente, questo ciclo:

 IEnumerable bar = ...; foreach (var foo in bar) { // do stuff } 

è zucchero sintattico per:

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

Se si sta iterando su una raccolta che implementa IEnumerable, è più naturale utilizzare foreach perché il membro successivo nell’iterazione viene assegnato nello stesso momento in cui viene eseguito il test per raggiungere la fine. Per esempio,

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

è più semplice di

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

È inoltre ansible utilizzare un ciclo for nell'implementazione della class di IEnumerable. È sufficiente che la propria implementazione GetEnumerator () utilizzi la parola chiave yield C # nel corpo del loop:

 yield return my_array[i]; 

Java ha entrambi i tipi di loop che hai indicato. È ansible utilizzare una delle varianti di ciclo for in base alle proprie esigenze. Il tuo bisogno può essere così

  1. Si desidera rieseguire l’indice dell’elemento di ricerca nell’elenco.
  2. Vuoi ottenere l’object stesso.

Nel primo caso dovresti usare il classico (stile c) per il ciclo. ma nel secondo caso dovresti usare il ciclo foreach .

Il ciclo foreach può essere utilizzato anche nel primo caso. ma in tal caso è necessario mantenere il proprio indice.

Se puoi fare ciò che ti serve con foreach allora foreach ; in caso contrario – per esempio, se hai bisogno della variabile indice stessa per qualche ragione – allora usa for . Semplice!

(E i tuoi due scenari sono ugualmente possibili con for o foreach .)

una ragione per non usare foreach almeno in java è che creerà un object iteratore che alla fine sarà raccolto. Quindi se stai cercando di scrivere codice che evita la garbage collection è meglio evitare foreach. Tuttavia, credo che sia ok per gli array puri perché non crea un iteratore.

Potrei pensare a diversi motivi

  1. non si can't mess up indexes , anche in ambiente mobile non si dispone di ottimizzazioni del compilatore e il ciclo scritto in modo approssimativo può eseguire diversi controlli di bounderay, dove per ogni ciclo fa solo 1.
  2. non è can't change data input size (aggiungi / rimuovi elementi) durante l’iterazione. Il tuo codice non lo frena facilmente. Se è necessario filtrare o trasformare i dati, utilizzare altri cicli.
  3. puoi scorrere su strutture di dati, che non possono essere accessi per indice, ma possono essere sottoposti a scansione. Per ogni necessità è necessario implementare iterable interface (java) o estendere IEnumerable (c #).
  4. è ansible avere una boiler plate più piccola, ad esempio quando si analizza l’XML è la differenza tra SAX e StAX, prima necessita di una copia in memoria del DOM per fare riferimento a un elemento quest’ultimo semplicemente itera su dati (non è veloce, ma è efficiente in memoria )

Si noti che se si sta cercando un elemento nell’elenco con per ciascuno, molto probabilmente lo si sta facendo in modo errato. Prendi in considerazione l’utilizzo di hashmap o bimap per saltare la ricerca tutti insieme.

Supponendo che il programmatore desideri utilizzare for loop come for each utilizzo di iteratori, esiste un errore comune di saltare elementi. Quindi in quella scena è più sicuro.

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

Parlando di codice pulito, una dichiarazione foreach è molto più veloce da leggere di una dichiarazione for!

Linq (in C #) può fare lo stesso, ma gli sviluppatori alle prime armi tendono ad avere difficoltà a leggerli!

Sembra che la maggior parte degli oggetti siano coperti … le seguenti sono alcune note extra che non vedo menzionate riguardo alle tue domande specifiche. Queste sono regole rigide rispetto alle preferenze di stile in un modo o nell’altro:

Desidero eseguire un’operazione su ciascun elemento in un elenco

In un ciclo foreach , non puoi modificare il valore della variabile di iterazione, quindi se stai cercando di cambiare il valore di un elemento specifico nel tuo elenco per cui devi usarlo.

Vale anche la pena notare che il modo “cool” è ora di utilizzare LINQ; ci sono molte risorse che puoi cercare se sei interessato.

foreach è l’ordine di grandezza più lento per l’implementazione della raccolta pesante.

Ho la prova. Queste sono le mie conclusioni

Ho usato il seguente semplice profiler per testare le loro prestazioni

 static void Main(string[] args) { DateTime start = DateTime.Now; List names = new List(); 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"); 

E ho ottenuto i seguenti risultati

Tempo impiegato = 11320,73 milli secondi (per loop)

Tempo impiegato = 11742.3296 milli secondi (ciclo foreach)

Un foreach ti avvisa anche se la raccolta che stai elencando attraverso le modifiche (cioè hai 7 elementi nella tua raccolta … fino a quando un’altra operazione su un thread separato ne ha rimossa una e ora hai solo 6 @ _ @)

Volevo solo aggiungere che chiunque pensi che foreach venga tradotto per e quindi non ha alcuna differenza di prestazioni è assolutamente sbagliato. Ci sono molte cose che accadono sotto il cofano, cioè l’enumerazione del tipo di object che NON è in un ciclo semplice. Sembra più un loop iteratore:

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

che è significativamente diverso da un semplice ciclo per. Se non capisci perché, allora cerca vtables. Inoltre, chi sa che cosa è nella funzione hasNext? Per quanto ne sappiamo potrebbe essere:

 while (!haveiwastedtheprogramstimeenough){ } now advance 

Esagerazione a parte, ci sono funzioni di implementazione sconosciuta e di efficienza chiamata. Dal momento che i compilatori non ottimizzano i confini delle funzioni, non vi è alcuna ottimizzazione in questo caso, ma solo la semplice ricerca vtable e la chiamata di funzione. Questa non è solo teoria, in pratica, ho visto significativi aumenti di velocità passando da foreach a for sulla C # ArrayList standard. Per essere onesti, era un arraylist con circa 30.000 articoli, ma ancora.