foreach esegue il suo corpo una volta per ogni elemento di una collezione, in ordine, senza nessun indice da gestire:
Output:
Welcome, Ava
Welcome, Noah
Welcome, Mia
Total: 255
[C][#][7]
Il ciclo funziona allo stesso modo su un array, una List<T>, una stringa (con elementi di tipo char), un HashSet<T>, una query LINQ o qualsiasi altra sequenza. Il tipo dichiarato può essere var, cosa comune quando il tipo degli elementi è lungo: foreach (var order in pendingOrders).
Cosa fa foreach dietro le quinte
foreach chiede alla collezione un enumeratore e lo percorre. Per una List<int>, il compilatore trasforma il ciclo più o meno in questo:
List<int>.Enumerator e = scores.GetEnumerator();
try
{
while (e.MoveNext())
{
int score = e.Current;
// loop body
}
}
finally
{
e.Dispose();
}
Ne seguono tre conseguenze. Funziona qualsiasi tipo con un metodo pubblico GetEnumerator(), che di solito si ottiene implementando IEnumerable<T>. L'enumeratore viene rilasciato anche quando il corpo lancia un'eccezione o esegue break. E la variabile dell'elemento è una copia nuova di e.Current a ogni passaggio, ed è per questo che assegnarla non può cambiare la collezione.
Un tipo senza GetEnumerator non si può usare:
error CS1579: foreach statement cannot operate on variables of type 'Order' because 'Order' does not contain a public instance or extension definition for 'GetEnumerator'
Implementa IEnumerable<T> sul tipo, oppure scorri la collezione che contiene (order.Lines invece di order). Puoi scrivere sequenze tue con yield return, trattato nella pagina su IEnumerable e yield.
Scorrere un Dictionary
Gli elementi di un dizionario sono valori KeyValuePair<TKey, TValue> con le proprietà Key e Value:
Output:
apples: 12
pears: 0
kiwis: 30
<apples><pears><kiwis>
Units in stock: 42
30 kiwis
12 apples
0 pears
Un Dictionary non garantisce alcun ordine di enumerazione. In pratica segue l'ordine di inserimento finché non rimuovi delle voci, ma il codice che dipende dall'ordine deve ordinare esplicitamente, come fa l'ultimo ciclo con OrderByDescending, oppure usare SortedDictionary.
Su .NET Core 2.0 e successivi (e in ogni .NET moderno), KeyValuePair ha un metodo Deconstruct, quindi la coppia si può dividere in due variabili direttamente nell'intestazione del ciclo:
// modern .NET: KeyValuePair deconstruction
foreach (var (fruit, count) in stock)
{
Console.WriteLine($"{fruit}: {count}");
}
È la forma più breve e quella che troverai nel codice attuale. Non compila su .NET Framework, dove KeyValuePair non ha Deconstruct; lì usa item.Key e item.Value.
Ottenere l'indice
foreach non ha un indice suo. I due modi abituali per ottenerne uno sono un contatore gestito da te, oppure l'overload di Select di LINQ che passa l'indice insieme a ogni elemento:
Output:
1. Kenji
2. Sofia
3. Omar
index 0: Kenji
index 1: Sofia
index 2: Omar
Il contatore è la soluzione più economica e più chiara. Se ti accorgi di usare l'indice per leggere podium[i - 1] o per scrivere podium[i] = ..., passa a un ciclo for, che è fatto proprio per questo.
La variabile del ciclo è di sola lettura
Non puoi assegnare un valore alla variabile di iterazione:
foreach (string name in names)
{
name = name.Trim();
// error CS1656: Cannot assign to 'name' because it is a 'foreach iteration variable'
}
Anche se fosse permesso, cambierebbe solo una copia locale. Per trasformare ogni elemento, costruisci una nuova sequenza (names.Select(n => n.Trim()).ToArray()) oppure usa un ciclo for che scrive names[i] = names[i].Trim().
Quello che puoi fare dipende dal fatto che l'elemento sia una classe o una struct. Per una classe, la variabile contiene un riferimento, quindi cambiare una proprietà cambia l'oggetto nella collezione:
Output:
Order 1: shipped
Order 2: shipped
Se Order fosse una struct, la stessa assegnazione fallirebbe con CS1654 (Cannot modify members of 'order' because it is a 'foreach iteration variable'), perché la variabile è una copia del valore, non un riferimento a esso.
Modificare la collezione: InvalidOperationException
Aggiungere o rimuovere elementi di una List o di un Dictionary mentre un foreach li percorre rompe l'enumeratore. Il MoveNext successivo lancia InvalidOperationException con il messaggio "Collection was modified; enumeration operation may not execute.":
Output:
InvalidOperationException: collection was modified
milk, bread
milk, bread
Le soluzioni, dalla più semplice alla più flessibile:
RemoveAll(predicate)su unaList<T>rimuove tutte le corrispondenze in un solo passaggio.- Scorri una copia con
ToList()oToArray(), poi modifica liberamente l'originale. - Prima raccogli, poi applica: durante il ciclo metti le chiavi da eliminare in una lista separata, poi rimuovile dopo. È lo schema abituale per un
Dictionary. - Un ciclo
forall'indietro conRemoveAt(i).
Dictionary ha un'eccezione che vale la pena conoscere. Da .NET Core 3.0, sovrascrivere il valore di una chiave esistente (stock[key] = 0) o rimuovere voci durante un foreach sul dizionario non lancia più eccezioni, mentre aggiungere una chiave nuova sì. Su .NET Framework tutte e tre le operazioni lanciano un'eccezione, quindi raccogliere prima le chiavi è la versione che funziona ovunque.
break, continue e collezioni null
break e continue si comportano come in qualsiasi altro ciclo:
Output:
reading 21
reading 23
no tags, no crash
Scorrere una collezione null lancia NullReferenceException quando viene chiamato GetEnumerator. ?? Enumerable.Empty<T>() trasforma una sequenza che può essere null in una vuota; meglio ancora, nei tuoi metodi restituisci collezioni vuote invece di null.
foreach o List.ForEach
List<T> ha un metodo ForEach(Action<T>): names.ForEach(n => Console.WriteLine(n));. Non è la stessa cosa del ciclo. Il suo corpo è una lambda, quindi break, continue e yield non sono disponibili e return termina solo la chiamata per l'elemento corrente. Esiste solo su List<T>, non sugli array né su altre sequenze. Preferisci l'istruzione foreach; usa ForEach solo per un'azione di una riga che hai già come metodo.
Domande frequenti
Come scorro un Dictionary con foreach in C#?
Ogni elemento è una KeyValuePair<TKey, TValue>: foreach (var pair in prices) Console.WriteLine($"{pair.Key}: {pair.Value}");. Su .NET Core 2.0 e successivi puoi decostruirla: foreach (var (name, price) in prices). Per scorrere solo le chiavi o solo i valori, usa prices.Keys o prices.Values.
Come ottengo l'indice in un ciclo foreach in C#?
foreach non ha un indice integrato. Tieni un contatore (int i = 0; prima del ciclo, i++ alla fine del corpo), oppure proietta ogni elemento con il suo indice usando LINQ: foreach (var item in names.Select((name, index) => new { name, index })). Se ti serve l'indice per riscrivere o confrontare elementi vicini, un ciclo for è più adatto.
Cosa significa "Collection was modified; enumeration operation may not execute"?
Hai aggiunto o rimosso elementi in una List, un Dictionary o un'altra collezione mentre un foreach la stava enumerando. L'enumeratore rileva la modifica e lancia InvalidOperationException al passo successivo. Scorri una copia (foreach (var x in list.ToList())), raccogli le modifiche e applicale dopo il ciclo, usa list.RemoveAll(...) oppure un ciclo for all'indietro.
Perché non posso assegnare un valore alla variabile di foreach in C#?
La variabile di iterazione è di sola lettura: name = name.Trim(); dentro foreach (string name in names) dà l'errore CS1656. Assegnarla comunque non cambierebbe la collezione. Per sostituire gli elementi usa un ciclo for con indice, oppure costruisci una nuova collezione con Select. Modificare una proprietà di un oggetto di una classe (order.Status = ...) è permesso.
Si può uscire da un ciclo foreach in C#?
Sì. break termina subito il ciclo e continue passa all'elemento successivo, esattamente come in for e while. return esce dall'intero metodo. Il metodo List<T>.ForEach è diverso: il suo corpo è una lambda, quindi lì break e continue non sono disponibili.