Na aula anterior, entendemos que todas as coleções compartilham a capacidade de serem percorridas, formalizada pelo contrato IEnumerable. Agora colhemos o fruto mais saboroso dessa fundação: o LINQ (sigla, em inglês, para "consulta integrada à linguagem", que se pronuncia "linc"). O LINQ é uma das ferramentas mais elegantes e queridas do C#, e sua ideia é transformadora. Até aqui, para extrair informação de uma coleção — filtrar os itens que interessam, transformá-los, ordená-los —, você escrevia laços manuais, dizendo como percorrer e testar cada elemento. Com o LINQ, você passa a descrever o que quer, de forma declarativa, e a ferramenta produz o resultado. É a diferença entre dar as instruções passo a passo e simplesmente enunciar o objetivo. Nesta aula, você conhecerá essa forma expressiva de trabalhar com dados, que mudará seu dia a dia de programador.
A mudança de mentalidade: descrever, não instruir
Comecemos sentindo a diferença que o LINQ traz, com um problema concreto. Temos uma lista de números e queremos apenas os pares, cada um dobrado, em ordem crescente. Com o que você sabe até agora, escreveria um conjunto de laços e passos:
List<int> numeros = new() { 5, 2, 8, 1, 4, 7, 6 };
// Forma imperativa: você diz COMO fazer, passo a passo.
List<int> resultado = new();
foreach (int n in numeros)
{
if (n % 2 == 0) // filtra os pares
resultado.Add(n * 2); // adiciona o dobro
}
resultado.Sort(); // ordena
Isso funciona, mas repare: a intenção ("os pares, dobrados, ordenados") fica soterrada sob a mecânica (o laço, o if, a lista intermediária, a ordenação). Agora veja a mesma tarefa com LINQ:
using System.Linq; // onde o LINQ vive (normalmente já vem incluído)
// Forma declarativa: você diz O QUE quer.
var resultado = numeros
.Where(n => n % 2 == 0) // apenas os pares
.Select(n => n * 2) // dobrados
.OrderBy(n => n); // em ordem crescente
Leia a versão LINQ quase como uma frase: "dos números, onde forem pares, selecione o dobro, ordene crescentemente". A consulta é a descrição do que se deseja. Não há laço visível, não há lista intermediária explícita, não há passos de ordenação separados — apenas a intenção, expressa diretamente. Essa é a mudança de mentalidade do LINQ: você deixa de dizer como percorrer e testar, e passa a dizer o que quer obter. O "como" fica por conta da ferramenta.
Uma primeira olhada na sintaxe estranha
Você deve estar intrigado com aquele n => n % 2 == 0 dentro do Where. Trata-se de uma pequena função anônima, chamada lambda, e ela é o coração de como o LINQ recebe seus critérios. Por ora, peço que a leia intuitivamente, sem se preocupar com a mecânica completa — que as duas próximas aulas revelarão em detalhe. A lambda n => n % 2 == 0 significa, aproximadamente: "para cada elemento, que chamarei de n, o critério é n % 2 == 0". O símbolo => (que já vimos no switch, na Aula #12) separa o "nome dado a cada elemento" (à esquerda) do "que fazer ou testar com ele" (à direita). Assim, Where(n => n % 2 == 0) lê-se "mantenha os elementos onde n é par", e Select(n => n * 2) lê-se "para cada elemento n, produza n * 2". Aceite essa leitura por enquanto; nas próximas aulas você entenderá exatamente o que é uma lambda e por que ela funciona. Aqui, o foco é usar o LINQ, e a leitura intuitiva basta.
Os operadores essenciais
O LINQ oferece muitos "operadores" — métodos que você encadeia para transformar uma coleção —, mas um punhado deles resolve a maioria dos casos. Conheçamos os mais importantes, todos usados com o ponto, sobre qualquer coleção.
O Where filtra: mantém apenas os elementos que satisfazem um critério.
var maioresQue3 = numeros.Where(n => n > 3); // só os elementos maiores que 3
O Select transforma: produz um novo valor a partir de cada elemento.
var quadrados = numeros.Select(n => n * n); // cada número ao quadrado
O OrderBy e o OrderByDescending ordenam, crescente e decrescentemente:
var crescente = numeros.OrderBy(n => n);
var decrescente = numeros.OrderByDescending(n => n);
E há operadores que produzem uma resposta única a partir da coleção — o que chamamos de agregações: Count (quantos), Sum (soma), Average (média), Max e Min (maior e menor):
Console.WriteLine(numeros.Count(n => n % 2 == 0)); // quantos são pares
Console.WriteLine(numeros.Sum()); // a soma de todos
Console.WriteLine(numeros.Max()); // o maior valor
Repare que esses operadores substituem, com uma única chamada expressiva, os padrões de processamento que aprendemos manualmente na Aula #21: o Count(...) faz a contagem condicional, o Sum() faz a acumulação, o Max() faz o "maior valor". Onde antes escrevíamos um laço com um acumulador, agora escrevemos uma palavra. O LINQ não faz nada que você não pudesse fazer com laços — ele apenas o expressa de forma muito mais concisa e legível.
O LINQ brilha sobre objetos
O verdadeiro poder do LINQ aparece quando trabalhamos com coleções de objetos — as classes que você aprendeu a criar. Suponha uma lista de produtos, e queremos os nomes dos produtos de certa categoria com preço abaixo de um limite, em ordem alfabética:
class Produto
{
public string Nome { get; set; }
public decimal Preco { get; set; }
public string Categoria { get; set; }
}
List<Produto> produtos = new()
{
new Produto { Nome = "Suco", Preco = 8m, Categoria = "Bebida" },
new Produto { Nome = "Refrigerante", Preco = 6m, Categoria = "Bebida" },
new Produto { Nome = "Água", Preco = 3m, Categoria = "Bebida" },
new Produto { Nome = "Pão", Preco = 5m, Categoria = "Padaria" }
};
// "Os nomes das bebidas abaixo de R$ 10, em ordem alfabética."
var bebidas = produtos
.Where(p => p.Categoria == "Bebida" && p.Preco < 10)
.OrderBy(p => p.Nome)
.Select(p => p.Nome);
foreach (var nome in bebidas)
Console.WriteLine(nome); // Água, Refrigerante, Suco
A consulta lê-se quase como a frase em português que a descreve: filtra as bebidas com preço abaixo de 10, ordena por nome, e seleciona os nomes. Dentro das lambdas, acessamos as properties dos objetos (p.Categoria, p.Preco, p.Nome) normalmente. Compare o esforço mental de escrever essa consulta com o de escrever os laços equivalentes: o LINQ reduz drasticamente a distância entre o que você pensa e o que você escreve. Essa proximidade entre a intenção e o código é a grande dádiva do LINQ, e é o que o torna tão querido.
Materializando o resultado
Uma observação prática importante, ligada à ideia de "entrega sob demanda" que plantamos na aula anterior. Uma consulta LINQ, quando escrita, ainda não executa de imediato — ela apenas descreve o que fazer. A execução acontece quando você consome o resultado, tipicamente ao percorrê-lo com foreach, ou ao pedir explicitamente que ele seja transformado numa lista concreta com o método ToList():
List<int> paresDobrados = numeros
.Where(n => n % 2 == 0)
.Select(n => n * 2)
.ToList(); // AQUI a consulta executa e produz uma List concreta
Na prática, a regra é simples: encadeie os operadores à vontade para descrever o que quer (é barato, pois nada roda ainda), e chame ToList() quando precisar do resultado como uma lista concreta que você vai guardar, percorrer mais de uma vez, ou passar adiante. Se você apenas vai percorrer o resultado uma vez com foreach, nem precisa do ToList() — o foreach já dispara a execução. Não se preocupe em dominar todas as sutilezas dessa "execução adiada" agora; guarde a regra prática do ToList(), e ela o servirá bem.
A forma de trabalhar com dados mudou de natureza. Em vez de escrever o passo a passo do percurso, passa-se a descrever o resultado desejado — filtrar, ordenar, transformar, agrupar — e deixar que a linguagem cuide da execução. Um bloco de laços e condições encolhe para uma expressão que se lê quase como uma frase.
Os operadores essenciais já cobrem a maior parte do uso diário, e a mecânica da materialização ficou registrada como o detalhe que evita surpresas: a consulta não roda no instante em que é escrita, e sim quando seu resultado é efetivamente percorrido ou convertido numa coleção. Sobre objetos próprios, essa expressividade é o que aproxima o código da linguagem do problema.
Fontes e leituras recomendadas
- Introdução ao LINQ — o panorama oficial de consultas LINQ; a base desta aula.
- Operadores de consulta padrão — o catálogo de
Where,Select,OrderBy, agregações e outros. - Filtragem de dados (Where) — o operador de filtro em detalhe.
- Projeção de dados (Select) — o operador de transformação em detalhe.
- Execução adiada e ToList — quando uma consulta LINQ de fato executa.
Exercícios
Exercício 1
Dada List<int> n = new() { 5, 2, 8, 1, 4, 7, 6 };, escreva uma consulta LINQ que retorne os números maiores que 3, cada um ao quadrado, em ordem crescente. Percorra o resultado com foreach e exiba-o. Depois, escreva a versão equivalente com laço manual e compare a legibilidade.
Ver resposta
✓ Resposta: Consulta:
var r = n.Where(x => x > 3).Select(x => x * x).OrderBy(x => x);
foreach (var v in r) Console.WriteLine(v);
Laço manual:
var r = new List<int>();
foreach (var x in n) if (x > 3) r.Add(x * x);
r.Sort();
As duas produzem o mesmo resultado, mas a versão LINQ expressa a intenção ("maiores que 3, ao quadrado, ordenados") numa linha declarativa, sem lista intermediária explícita nem passo de ordenação separado, enquanto a imperativa mistura filtro, transformação e ordenação em mecânica manual mais verbosa.
Exercício 2
Usando os operadores de agregação, escreva expressões LINQ que calculem, para a lista acima: a soma de todos os números; quantos são pares; e o maior número. Relacione cada uma com um dos "padrões de processamento" que você aprendeu manualmente na Aula #21.
Ver resposta
✓ Resposta: Exemplos:
Console.WriteLine(n.Sum()); // soma → padrão de acumulação
Console.WriteLine(n.Count(x => x % 2 == 0)); // pares → padrão de contagem condicional
Console.WriteLine(n.Max()); // maior → padrão de maior/menor
Cada agregação LINQ corresponde a um padrão da Aula #21: o Sum() substitui o laço acumulador da acumulação; o Count(x => ...) substitui o laço com if e contador da contagem condicional; o Max() substitui o laço que rastreia o maior valor. O LINQ expressa em uma palavra o que antes era um laço inteiro.
Exercício 3
Crie uma List<Produto> (com properties Nome, Preco, Categoria) com alguns itens, e escreva uma consulta LINQ que retorne os nomes dos produtos da categoria "Padaria" com preço abaixo de 10, em ordem alfabética. Exiba o resultado.
Ver resposta
✓ Resposta: Exemplo:
List<Produto> produtos = new()
{
new Produto { Nome = "Pão", Preco = 5m, Categoria = "Padaria" },
new Produto { Nome = "Bolo", Preco = 25m, Categoria = "Padaria" },
new Produto { Nome = "Biscoito", Preco = 4m, Categoria = "Padaria" }
};
var itens = produtos
.Where(p => p.Categoria == "Padaria" && p.Preco < 10)
.OrderBy(p => p.Nome)
.Select(p => p.Nome);
foreach (var nome in itens) Console.WriteLine(nome); // Biscoito, Pão
Exercício 4
Explique, com suas palavras, a diferença entre a abordagem imperativa (laços manuais) e a declarativa (LINQ) para processar coleções. O que significa dizer que, com o LINQ, "você descreve o que quer, não como fazer"?
Ver resposta
✓ Resposta: A abordagem imperativa (laços manuais) consiste em escrever, passo a passo, como percorrer a coleção e o que fazer com cada elemento — o laço, a condição, a coleção intermediária, a ordenação, tudo explícito. A abordagem declarativa (LINQ) consiste em descrever o resultado desejado — quais elementos manter, como transformá-los, como ordená-los — deixando a mecânica de percorrer por conta da ferramenta. Dizer que "com o LINQ você descreve o que quer, não como fazer" significa que você enuncia o objetivo (por exemplo, "os pares, dobrados, ordenados") em vez de detalhar os passos para alcançá-lo; a intenção fica visível no código, e os detalhes de execução ficam ocultos, tornando o código mais legível e mais próximo do raciocínio humano.
Exercício 5
Sem código: a aula afirma que o LINQ "não faz nada que você não pudesse fazer com laços". Se é assim, qual é, então, o valor do LINQ? Por que vale a pena aprendê-lo, se os laços já resolviam os mesmos problemas?
Ver resposta
✓ Resposta: O valor do LINQ não está em fazer algo impossível com laços — de fato, tudo que ele faz poderia ser feito manualmente —, mas em como ele expressa esse processamento: de forma muito mais concisa, legível e próxima da intenção. Vale a pena aprendê-lo porque a legibilidade é um dos maiores valores na programação: uma consulta LINQ de três linhas comunica seu propósito imediatamente ("filtra, transforma, ordena"), enquanto o laço equivalente exige que quem lê reconstrua a intenção a partir da mecânica. Além disso, o LINQ reduz a chance de erros (não há índices para errar, nem listas intermediárias para gerenciar) e torna o código mais fácil de modificar. Em resumo, o LINQ não amplia o que você pode fazer, mas melhora enormemente como você o faz — e código mais claro e menos propenso a erros é um ganho que justifica plenamente o aprendizado.