Segredos do foreach em C#: como obter o índice da iteração atual

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que o foreach não fornece o índice "pronto para uso"? E o que fazer com isso?

\n\n

Você 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.

\n\n

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á!

\n\n

Método nº 1. Clássico: contador externo

\n\n

O método mais simples e intuitivo. Criamos uma variável contadora antes do loop e a incrementamos no corpo.

\n\n
List<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\n

Pró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
\n\n

Contras:

\n
    \n
  • É preciso lembrar de declarar e incrementar o contador.
  • \n
  • Se houver continue dentro 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
\n\n

Este método é como um canivete suíço: confiável, mas básico. Para 80% das tarefas, é mais do que suficiente.

\n\n

Método nº 2. LINQ Select com índice: elegância em uma linha

\n\n

Se você gosta de estilo funcional, o LINQ vem em seu socorro. O método Select() tem uma sobrecarga que passa o índice do elemento.

\n\n
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\n

Observe 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\n

Pró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
\n\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
\n\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\n

Mé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:

\n\n
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\n

Pare! 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

Blogs

Book Recommendations