Você já sabe guardar muitos valores, em arrays e em listas. Mas uma coleção só ganha utilidade quando extraímos informação dela: qual é a soma de todas as notas? Quantos alunos foram aprovados? Qual é o maior valor? Existe um nome específico na lista? Essas perguntas se respondem percorrendo a coleção — visitando cada elemento — e processando os valores conforme os visitamos. Nesta aula, que fecha a Fase 4, você aprenderá os padrões clássicos de processamento de coleções: a soma, a contagem condicional, a busca, e a determinação de maior e menor. Não são conceitos novos — são combinações de laços (Fase 2) e coleções (esta fase) —, mas reuni-los em padrões reconhecíveis é o que transforma você de alguém que sabe as peças em alguém que sabe montá-las para resolver problemas reais.
Retomando o foreach em sua plenitude
Ao longo das últimas fases, encontramos o foreach algumas vezes, sempre prometendo que ele ganharia pleno sentido quando tivéssemos coleções de verdade. Chegou a hora. O foreach percorre qualquer coleção — array ou List — visitando cada elemento, sem que precisemos gerenciar índices ou tamanho:
List<int> notas = new() { 8, 6, 9, 7, 10 };
foreach (int nota in notas) // para cada 'nota' em 'notas'...
{
Console.WriteLine(nota); // ...processa aquela nota.
}
O foreach é a forma preferida de percorrer uma coleção quando você só precisa visitar cada elemento, sem se importar com sua posição. Ele é mais limpo e mais seguro que o for: sem contador para inicializar, sem condição de parada para acertar, sem risco de acessar um índice fora dos limites. Você diz "para cada elemento, faça isto", e o foreach cuida de percorrer tudo, do primeiro ao último. Nos padrões que veremos a seguir, o foreach será nossa ferramenta principal; recorreremos ao for apenas quando a posição de fato importar.
Padrão 1: acumulação (soma, total)
O primeiro padrão fundamental é a acumulação: percorrer a coleção mantendo uma variável que acumula um resultado ao longo do caminho. O exemplo canônico é somar todos os valores:
List<int> notas = new() { 8, 6, 9, 7, 10 };
int soma = 0; // o "acumulador", começa em zero (elemento neutro da soma)
foreach (int nota in notas)
{
soma = soma + nota; // a cada nota, acrescenta-a ao acumulador
}
Console.WriteLine($"Soma: {soma}"); // Soma: 40
A estrutura deste padrão é sempre a mesma, e vale reconhecê-la: uma variável acumuladora é criada antes do laço, com um valor inicial apropriado (zero para soma); dentro do laço, cada elemento é combinado com o acumulador; ao final, o acumulador contém o resultado. A partir da soma, a média é imediata — basta dividir pela quantidade de elementos (soma / notas.Count, com atenção à divisão inteira que vimos na Aula #06). O padrão de acumulação serve não só para somar, mas para qualquer resultado que se constrói item a item: um produto (acumulador inicia em 1), uma concatenação de textos (acumulador inicia em texto vazio), e mais.
Padrão 2: contagem condicional
Frequentemente queremos saber quantos elementos satisfazem uma certa condição — quantas notas são de aprovação, quantos preços estão acima de um limite. O padrão é uma acumulação especializada: um contador que só aumenta quando a condição é verdadeira:
List<int> notas = new() { 8, 4, 9, 5, 10, 3 };
int aprovados = 0;
foreach (int nota in notas)
{
if (nota >= 6) // se a nota é de aprovação...
{
aprovados = aprovados + 1; // ...conta mais um aprovado
}
}
Console.WriteLine($"Aprovados: {aprovados}"); // Aprovados: 3
Note a combinação de ferramentas: o foreach percorre, o if (Fase 2) decide se cada elemento conta, e o contador acumula. Este padrão — percorrer, testar uma condição, contar os que passam — resolve uma enorme variedade de perguntas do tipo "quantos...?". Trocando a condição do if, você conta quantos são pares, quantos começam com certa letra, quantos estão dentro de uma faixa. A estrutura permanece; só a condição muda.
Padrão 3: busca
Buscar é procurar um elemento específico ou verificar se ele existe. Para coleções, já vimos que a List oferece o método Contains, que faz isso prontamente — e, quando ele serve, é a melhor escolha. Mas é instrutivo entender o padrão de busca "manual", que se aplica a casos mais complexos (buscar um elemento que satisfaça uma condição, não apenas um valor exato):
List<string> nomes = new() { "Ana", "Beto", "Carla" };
bool encontrou = false;
foreach (string nome in nomes)
{
if (nome == "Beto")
{
encontrou = true;
break; // achamos: não precisa continuar procurando (Aula #14)
}
}
Console.WriteLine(encontrou ? "Encontrado!" : "Não encontrado.");
Dois pontos valem destaque. Primeiro, o break (Aula #14): assim que encontramos o que procurávamos, interrompemos o laço, pois continuar seria desperdício. Segundo, a última linha usa um recurso conciso, o operador condicional (condição ? valorSeVerdadeiro : valorSeFalso), que produz um de dois valores conforme a condição — aqui, "Encontrado!" se encontrou for verdadeiro, "Não encontrado." caso contrário. É uma forma compacta de um if/else que produz um valor; mencione-o como uma conveniência que você encontrará com frequência. (Para uma busca por valor exato numa List, lembre-se: nomes.Contains("Beto") faz o mesmo em uma linha; o padrão manual brilha quando a condição de busca é mais elaborada.)
Padrão 4: maior e menor
Encontrar o maior (ou o menor) valor de uma coleção é outro padrão clássico, e tem uma sutileza no valor inicial que vale entender. A ideia: percorrer mantendo uma variável com o "maior até agora", atualizando-a sempre que encontrar algo maior:
List<int> valores = new() { 23, 67, 12, 89, 45 };
int maior = valores[0]; // começa assumindo que o PRIMEIRO é o maior
foreach (int v in valores)
{
if (v > maior) // se encontrar um valor maior que o "maior até agora"...
{
maior = v; // ...ele passa a ser o maior
}
}
Console.WriteLine($"Maior valor: {maior}"); // Maior valor: 89
A sutileza está no valor inicial de maior: começamos com o primeiro elemento da coleção (valores[0]), e não com zero. Por quê? Porque zero seria um chute arbitrário — se todos os valores fossem negativos, o zero (que não está na coleção) seria erroneamente reportado como o maior. Iniciar com um elemento real da coleção garante que a resposta seja sempre um valor que de fato existe nela. Este é um cuidado importante do padrão maior/menor: o valor inicial deve ser um elemento genuíno, tipicamente o primeiro. (Para o menor, a lógica é idêntica, trocando > por <.) Vale mencionar que, para casos simples, a própria coleção oferece atalhos prontos — mas conhecer o padrão manual ensina o raciocínio e prepara você para variações que os atalhos não cobrem.
Uma visão de conjunto dos padrões
Recapitulando os quatro padrões que você agora domina: a acumulação constrói um resultado item a item (soma, produto, total); a contagem condicional conta quantos elementos satisfazem uma condição; a busca localiza um elemento ou verifica sua existência; e o maior/menor encontra os extremos. Todos compartilham a mesma espinha dorsal — percorrer a coleção com foreach, processando cada elemento — variando apenas o que se faz a cada passo. Reconhecer qual padrão um problema pede é meio caminho para resolvê-lo, e você os aplicará constantemente. Eles são as ferramentas com que transformamos coleções de meros depósitos de dados em fontes de respostas úteis.
Guardar uma coleção é pouco; o valor aparece ao extrair informação dela. Os quatro padrões vistos aqui cobrem a enorme maioria do que se faz com dados no dia a dia: acumular um total, contar quantos itens satisfazem um critério, localizar um item específico e descobrir o maior ou o menor.
O que eles têm em comum é mais importante do que suas diferenças. Todos declaram uma variável antes do laço, ajustam essa variável a cada item e usam o resultado depois que o percurso termina. Reconhecer essa forma recorrente transforma problemas aparentemente distintos em variações de um mesmo esqueleto, e é isso que permite resolver um caso novo sem procurar uma receita pronta.
Fontes e leituras recomendadas
- Percorrendo coleções (foreach) — a referência do laço de percurso, base dos padrões desta aula.
- Padrões comuns de processamento de dados — como operar sobre coleções, contexto para os padrões apresentados.
- O operador condicional (? :) — o operador compacto usado no padrão de busca.
- Métodos de agregação em coleções — os atalhos prontos (soma, máximo) que a linguagem oferece, para conhecer.
- Boas práticas com laços e coleções — orientações de clareza ao processar conjuntos de dados.
Exercícios
Exercício 1
Crie uma List<int> com sete números à sua escolha e use o padrão de acumulação para calcular e exibir a soma de todos eles. Depois, calcule e exiba a média, tomando cuidado com a divisão (converta para double se quiser casas decimais).
Ver resposta
✓ Resposta: Exemplo:
List<int> numeros = new() { 4, 8, 15, 16, 23, 42, 10 };
int soma = 0;
foreach (int n in numeros) soma += n;
Console.WriteLine($"Soma: {soma}"); // 118
double media = (double)soma / numeros.Count;
Console.WriteLine($"Média: {media}"); // 16.857...
A acumulação soma os sete valores; a média divide a soma pela quantidade (Count), com a conversão para double garantindo casas decimais.
Exercício 2
Crie uma List<int> com as idades de várias pessoas e use o padrão de contagem condicional para contar e exibir quantas são maiores de idade (18 ou mais). Depois, modifique para contar quantas são menores de idade, e verifique que as duas contagens somam o total.
Ver resposta
✓ Resposta: Exemplo:
List<int> idades = new() { 15, 22, 17, 30, 8, 19 };
int maiores = 0, menores = 0;
foreach (int idade in idades)
{
if (idade >= 18) maiores++;
else menores++;
}
Console.WriteLine($"Maiores: {maiores}, Menores: {menores}"); // Maiores: 3, Menores: 3
O padrão de contagem conta quantas idades satisfazem cada condição; a soma das duas contagens (3 + 3 = 6) iguala o total de elementos, confirmando que todos foram classificados.
Exercício 3
Crie uma List<string> com nomes e use o padrão de busca (com foreach e break) para verificar se um nome específico está presente, exibindo uma mensagem apropriada. Depois, reescreva a mesma verificação usando o método Contains e compare as duas abordagens.
Ver resposta
✓ Resposta: Com foreach e break:
List<string> nomes = new() { "Ana", "Beto", "Carla" };
bool achou = false;
foreach (string nome in nomes)
if (nome == "Carla") { achou = true; break; }
Console.WriteLine(achou ? "Presente" : "Ausente");
Com Contains:
Console.WriteLine(nomes.Contains("Carla") ? "Presente" : "Ausente");
As duas produzem o mesmo resultado. O Contains é muito mais conciso e é a escolha preferível para busca por valor exato; o padrão manual com foreach/break é instrutivo e torna-se necessário quando a busca envolve uma condição mais elaborada que a simples igualdade.
Exercício 4
Crie uma List<int> com valores variados (incluindo alguns negativos) e use o padrão de maior/menor para encontrar e exibir o maior e o menor valor. Explique por que é importante iniciar as variáveis maior e menor com o primeiro elemento da lista, e não com zero.
Ver resposta
✓ Resposta: Exemplo:
List<int> valores = new() { -5, 12, -20, 8, 3 };
int maior = valores[0];
int menor = valores[0];
foreach (int v in valores)
{
if (v > maior) maior = v;
if (v < menor) menor = v;
}
Console.WriteLine($"Maior: {maior}, Menor: {menor}"); // Maior: 12, Menor: -20
É importante iniciar maior e menor com o primeiro elemento da lista porque zero é um valor arbitrário que pode não pertencer à coleção: se todos os valores fossem negativos (como parte deste exemplo), iniciar maior com zero reportaria zero como o maior, o que estaria errado, pois zero não está na lista. Iniciar com um elemento real (valores[0]) garante que o resultado seja sempre um valor que de fato existe na coleção.
Exercício 5
Sem código: os quatro padrões desta aula (acumulação, contagem condicional, busca, maior/menor) compartilham uma "espinha dorsal" comum. Descreva-a com suas palavras, e explique o que varia de um padrão para outro. Por que reconhecer o padrão que um problema pede facilita a solução?
Ver resposta
✓ Resposta: A espinha dorsal comum é: percorrer a coleção com um foreach, processando cada elemento à medida que é visitado, tipicamente atualizando uma variável mantida fora do laço. O que varia de um padrão para outro é o que se faz com cada elemento: na acumulação, combina-se o elemento a um acumulador (somando, por exemplo); na contagem condicional, testa-se uma condição e incrementa-se um contador quando ela é verdadeira; na busca, compara-se o elemento ao alvo e interrompe-se ao encontrá-lo; no maior/menor, compara-se o elemento ao extremo atual e atualiza-se quando apropriado. Reconhecer o padrão que um problema pede facilita a solução porque, identificado o padrão, a estrutura do código já está dada — resta apenas preencher a condição ou a operação específica —, o que transforma um problema aparentemente novo numa aplicação de uma receita já conhecida.