Por que o foreach não fornece o índice "pronto para uso"? E o que fazer com isso?
\n\nVocê já escreveu um loop foreach em C# e de repente percebeu: "Preciso do número do elemento atual!". Sua mão vai em direção ao teclado para escrever for (int i = 0; i < list.Count; i++)... Pare! E se a coleção for um IEnumerable que não possui indexador? Ou você está trabalhando com HashSet? Aqui, o bom e velho for não funciona.
Esta pergunta é um verdadeiro sucesso no Stack Overflow (milhões de visualizações!). E não é à toa. foreach é o rei da iteração de coleções: é conciso, seguro e funciona com qualquer tipo enumerável. Mas sua maldição é a falta de um contador embutido. Hoje vamos corrigir isso. Vou mostrar 4 maneiras comprovadas de obter o índice no foreach, analisar seus prós, contras e desempenho. Vamos lá!
Método nº 1. Clássico: contador externo
\n\nO método mais simples e intuitivo. Criamos uma variável contadora antes do loop e a incrementamos no corpo.
\n\nList<string> fruits = new List<string>() { "Maçã", "Banana", "Cereja", "Melão" };\nint index = 0;\nforeach (var fruit in fruits)\n{\n Console.WriteLine($"Índice {index}: {fruit}");\n index++;\n}\n\nPrós:
\n- \n
- Extremamente claro. O código é lido em um segundo. \n
- Não requer bibliotecas adicionais ou LINQ. \n
- Funciona com qualquer
IEnumerable. \n
Contras:
\n- \n
- É preciso lembrar de declarar e incrementar o contador. \n
- Se houver
continuedentro do loop, o contador ainda será incrementado (é isso que você quer?). \n - Em loops aninhados, é fácil se confundir sobre qual index se refere a qual. \n
Este método é como um canivete suíço: confiável, mas básico. Para 80% das tarefas, é mais do que suficiente.
\n\nMétodo nº 2. LINQ Select com índice: elegância em uma linha
\n\nSe você gosta de estilo funcional, o LINQ vem em seu socorro. O método Select() tem uma sobrecarga que passa o índice do elemento.
List<string> fruits = new List<string>() { "Maçã", "Banana", "Cereja", "Melão" };\n\nforeach (var (fruit, index) in fruits.Select((item, i) => (item, i)))\n{\n Console.WriteLine($"Índice {index}: {fruit}");\n}\n\nObserve a desconstrução de tupla diretamente no cabeçalho do loop. Este é um dos recursos mais bonitos do C# moderno (a partir da versão 7.0).
\n\nPrós:
\n- \n
- O código se torna declarativo: "pegue a coleção, adicione a cada elemento seu índice". \n
- O contador não vaza para fora — ele é encapsulado dentro do LINQ. \n
- Combina perfeitamente com cadeias de outros métodos LINQ. \n
Contras:
\n- \n
- Pequeno overhead na criação de tuplas. Para coleções de até 10.000 elementos, é imperceptível, mas para listas gigantescas pode ser crítico. \n
- Requer importação de
System.Linq. \n
Este método é a escolha de quem escreve código "limpo". É especialmente bom quando o índice não é necessário para controlar o loop, mas para passar para outro método.
\n\nMétodo nº 3. Indexador via ElementAt(): quando a coleção é IEnumerable
\n\nÀs vezes, você tem apenas um IEnumerable, sem indexador, mas realmente deseja obter o elemento por número. Tecnicamente, você pode fazer assim:
IEnumerable<string> fruits = new List<string>() { "Maçã", "Banana", "Cereja", "Melão" };\n\nfor (int i = 0; i < fruits.Count(); i++) // Atenção: este é um código ruim!\n{\n var fruit = fruits.ElementAt(i);\n Console.WriteLine($"Índice {i}: {fruit}");\n}\n\nPare! Nunca faça isso. Count() e ElementAt() para IEnumerable funcionam em O(n) cada. Seu loop se tornará O(n²) — complexidade quadrática. Em 1000