O foreach executa o corpo uma vez para cada elemento de uma coleção, em ordem, sem índice para administrar:
Saída:
Welcome, Ava
Welcome, Noah
Welcome, Mia
Total: 255
[C][#][7]
O laço funciona do mesmo jeito em um array, uma List<T>, uma string (tipo de elemento char), um HashSet<T>, uma consulta LINQ ou qualquer outra sequência. O tipo declarado pode ser var, o que é comum quando o tipo do elemento é longo: foreach (var order in pendingOrders).
O que o foreach faz por trás
O foreach pede um enumerador à coleção e o percorre. Para uma List<int>, o compilador transforma o laço mais ou menos nisto:
List<int>.Enumerator e = scores.GetEnumerator();
try
{
while (e.MoveNext())
{
int score = e.Current;
// loop body
}
}
finally
{
e.Dispose();
}
Daí vêm três consequências. Qualquer tipo com um método público GetEnumerator() funciona, e normalmente ele vem da implementação de IEnumerable<T>. O enumerador é descartado mesmo quando o corpo lança uma exceção ou executa break. E a variável do elemento é uma cópia nova de e.Current a cada passada, e é por isso que atribuir a ela não muda a coleção.
Um tipo sem GetEnumerator não pode ser usado:
error CS1579: foreach statement cannot operate on variables of type 'Order' because 'Order' does not contain a public instance or extension definition for 'GetEnumerator'
Implemente IEnumerable<T> no tipo, ou percorra a coleção que ele contém (order.Lines em vez de order). Você pode escrever suas próprias sequências com yield return, assunto da página de IEnumerable e yield.
Percorrendo um Dictionary
Os elementos de um dicionário são valores KeyValuePair<TKey, TValue> com as propriedades Key e Value:
Saída:
apples: 12
pears: 0
kiwis: 30
<apples><pears><kiwis>
Units in stock: 42
30 kiwis
12 apples
0 pears
Um Dictionary não garante nenhuma ordem de enumeração. Na prática ele segue a ordem de inserção até você remover entradas, mas código que depende da ordem deve ordenar explicitamente, como o último laço faz com OrderByDescending, ou usar SortedDictionary.
No .NET Core 2.0 em diante (e em todo .NET moderno), KeyValuePair tem um método Deconstruct, então o par pode ser separado em duas variáveis direto no cabeçalho do laço:
// modern .NET: KeyValuePair deconstruction
foreach (var (fruit, count) in stock)
{
Console.WriteLine($"{fruit}: {count}");
}
Essa é a forma mais curta e a que você vai ver no código atual. Ela não compila no .NET Framework, onde KeyValuePair não tem Deconstruct; lá, use item.Key e item.Value.
Obtendo o índice
O foreach não tem índice próprio. As duas formas usuais de obter um são um contador que você mantém, ou a sobrecarga de Select do LINQ que passa o índice junto com cada elemento:
Saída:
1. Kenji
2. Sofia
3. Omar
index 0: Kenji
index 1: Sofia
index 2: Omar
O contador é o mais barato e o mais claro. Se você perceber que está usando o índice para ler podium[i - 1] ou para escrever podium[i] = ..., troque para um laço for, que foi feito exatamente para isso.
A variável do laço é somente leitura
Você não pode atribuir à variável de iteração:
foreach (string name in names)
{
name = name.Trim();
// error CS1656: Cannot assign to 'name' because it is a 'foreach iteration variable'
}
Mesmo se fosse permitido, só mudaria uma cópia local. Para transformar cada elemento, monte uma nova sequência (names.Select(n => n.Trim()).ToArray()) ou use um laço for que escreve names[i] = names[i].Trim().
O que você pode fazer depende de o elemento ser uma classe ou uma struct. Para uma classe, a variável guarda uma referência, então mudar uma propriedade muda o objeto dentro da coleção:
Saída:
Order 1: shipped
Order 2: shipped
Se Order fosse uma struct, a mesma atribuição falharia com CS1654 (Cannot modify members of 'order' because it is a 'foreach iteration variable'), porque a variável é uma cópia do valor, não uma referência a ele.
Modificando a coleção: InvalidOperationException
Adicionar ou remover elementos de uma List ou de um Dictionary enquanto um foreach a percorre quebra o enumerador. O próximo MoveNext lança InvalidOperationException com a mensagem "Collection was modified; enumeration operation may not execute.":
Saída:
InvalidOperationException: collection was modified
milk, bread
milk, bread
As soluções, da mais simples para a mais flexível:
RemoveAll(predicate)em umaList<T>remove todas as correspondências de uma vez.- Percorrer uma cópia com
ToList()ouToArray(), e então modificar o original à vontade. - Juntar primeiro, aplicar depois: guarde as chaves a apagar em uma lista separada durante o laço e remova-as em seguida. É o padrão usual para um
Dictionary. - Um laço
forde trás para frente comRemoveAt(i).
O Dictionary tem uma exceção que vale conhecer. Desde o .NET Core 3.0, sobrescrever o valor de uma chave existente (stock[key] = 0) ou remover entradas durante um foreach sobre ele não lança mais exceção, mas adicionar uma chave nova ainda lança. No .NET Framework as três lançam, então juntar as chaves primeiro é a versão que funciona em qualquer lugar.
break, continue e coleções nulas
break e continue se comportam como em qualquer outro laço:
Saída:
reading 21
reading 23
no tags, no crash
Percorrer uma coleção null lança NullReferenceException quando GetEnumerator é chamado. ?? Enumerable.Empty<T>() transforma uma sequência que pode ser nula em uma vazia; melhor ainda, retorne coleções vazias em vez de null nos seus próprios métodos.
foreach versus List.ForEach
List<T> tem um método ForEach(Action<T>): names.ForEach(n => Console.WriteLine(n));. Não é a mesma coisa que o laço. O corpo dele é uma lambda, então break, continue e yield não estão disponíveis e return só encerra a chamada do elemento atual. Ele só existe em List<T>, não em arrays nem em outras sequências. Prefira a instrução foreach; use ForEach só para uma ação de uma linha que você já tem como método.
Perguntas frequentes
Como percorrer um Dictionary com foreach em C#?
Cada elemento é um KeyValuePair<TKey, TValue>: foreach (var pair in prices) Console.WriteLine($"{pair.Key}: {pair.Value}");. No .NET Core 2.0 em diante você pode desconstruí-lo: foreach (var (name, price) in prices). Para percorrer só as chaves ou só os valores, use prices.Keys ou prices.Values.
Como obter o índice em um laço foreach em C#?
O foreach não tem índice próprio. Mantenha um contador (int i = 0; antes do laço, i++ no fim do corpo), ou projete cada elemento com seu índice usando LINQ: foreach (var item in names.Select((name, index) => new { name, index })). Se você precisa do índice para escrever de volta ou comparar vizinhos, um laço for é a melhor escolha.
O que significa "Collection was modified; enumeration operation may not execute"?
Você adicionou ou removeu itens de uma List, um Dictionary ou outra coleção enquanto um foreach a percorria. O enumerador detecta a mudança e lança InvalidOperationException no passo seguinte. Percorra uma cópia (foreach (var x in list.ToList())), junte as mudanças e aplique depois do laço, use list.RemoveAll(...), ou use um laço for de trás para frente.
Por que não posso atribuir à variável do foreach em C#?
A variável de iteração é somente leitura: name = name.Trim(); dentro de foreach (string name in names) é o erro CS1656. Atribuir a ela também não mudaria a coleção. Para substituir elementos, use um laço for com índice, ou monte uma nova coleção com Select. Mudar uma propriedade de um objeto de classe (order.Status = ...) é permitido.
Dá para sair de um laço foreach com break em C#?
Sim. break encerra o laço imediatamente e continue pula para o próximo elemento, exatamente como em for e while. return sai do método inteiro. O método List<T>.ForEach é diferente: o corpo dele é uma lambda, então break e continue não estão disponíveis ali.