Módulo 1 · C# — Capítulo 09

LINQ

A ferramenta que você vai usar todos os dias: consultar, filtrar e transformar coleções de forma declarativa — o "map/filter/reduce" do C#, e mais.

1. LINQ é map/filter/reduce, e mais

Se você já é confortável com .map(), .filter() e .reduce() em JavaScript, LINQ (Language Integrated Query) vai parecer natural — é o mesmo estilo de programação funcional aplicado a qualquer coleção (IEnumerable<T>), com muito mais operadores prontos.

JavaScriptLINQ (C#)
.map(x => x.nome).Select(x => x.Nome)
.filter(x => x.ativo).Where(x => x.Ativo)
.reduce((a,b) => a+b, 0).Sum() / .Aggregate(...)
.find(x => ...).FirstOrDefault(x => ...)
.some(x => ...).Any(x => ...)
.every(x => ...).All(x => ...)
.sort((a,b) => ...).OrderBy(x => ...)

2. Sintaxe de método (a mais usada)

C# encadeando operadores LINQ
var clientes = new List<Cliente>
{
    new() { Nome = "Ana",   Ativo = true,  TotalCompras = 1500m },
    new() { Nome = "Bruno", Ativo = false, TotalCompras = 200m  },
    new() { Nome = "Carla", Ativo = true,  TotalCompras = 3200m },
};

var nomesAtivosOrdenados = clientes
    .Where(c => c.Ativo)
    .OrderByDescending(c => c.TotalCompras)
    .Select(c => c.Nome)
    .ToList();
// ["Carla", "Ana"]

decimal totalGeral = clientes.Sum(c => c.TotalCompras);
bool existeVip = clientes.Any(c => c.TotalCompras > 3000);
Cliente? maiorComprador = clientes.OrderByDescending(c => c.TotalCompras).FirstOrDefault();
Nota

Cada operador (.Where, .Select...) devolve uma nova sequência — a lista original nunca é alterada. É o mesmo princípio de imutabilidade dos métodos de array do JS (.map/.filter também não alteram o array original).

3. Execução adiada (deferred execution)

Uma diferença real de comportamento: a maior parte dos operadores LINQ não executa imediatamente — eles só descrevem a operação. A execução de fato só acontece quando você itera o resultado (foreach) ou chama algo que força a materialização, como .ToList() ou .Count().

C# execução adiada — pegadinha comum
var numeros = new List<int> { 1, 2, 3 };

var pares = numeros.Where(n => n % 2 == 0); // ainda não rodou nada

numeros.Add(4);

foreach (var n in pares) // AGORA a query roda, sobre a lista JÁ com o 4
    Console.WriteLine(n); // imprime 2 e 4 — não só 2!
Atenção

Se você precisa "congelar" o resultado no momento em que a query foi escrita, force a execução imediata com .ToList(). Isso é especialmente importante quando a fonte de dados pode mudar entre a definição da query e o momento em que ela é usada — e crítico quando a "coleção" na verdade é uma tabela do banco via Entity Framework (módulo de integração), onde .ToList() é o que decide quando a query realmente vai ao banco.

4. Agrupamento com GroupBy

O equivalente LINQ do GROUP BY que você já usou intensamente no Módulo 2:

C# GroupBy
var pedidos = new List<Pedido> { /* ... */ };

var totalPorCliente = pedidos
    .GroupBy(p => p.ClienteId)
    .Select(grupo => new
    {
        ClienteId = grupo.Key,
        TotalPedidos = grupo.Count(),
        ValorTotal = grupo.Sum(p => p.Valor)
    })
    .OrderByDescending(x => x.ValorTotal)
    .ToList();

Repare a semelhança estrutural com o SQL do capítulo 6 do Módulo 2 (GROUP BY + SUM/COUNT) — LINQ é, em essência, SQL expresso como uma cadeia de métodos C#, operando sobre objetos em memória em vez de linhas de tabela.

5. Tipos anônimos

new {'{'} ClienteId = grupo.Key, ... {'}'} no exemplo acima cria um tipo anônimo — uma classe sem nome, gerada pelo compilador, útil quando você precisa de uma forma de dado temporária que não justifica criar uma classe nomeada:

C# tipo anônimo
var resumo = new { Nome = "Ana", Total = 1500m };
Console.WriteLine(resumo.Nome); // "Ana" — propriedades acessíveis normalmente
// mas resumo não tem um nome de tipo que você possa escrever em outro lugar

6. Sintaxe de query (menos comum, mas você vai encontrá-la)

LINQ também tem uma sintaxe parecida com SQL de verdade — funcionalmente idêntica à sintaxe de método, só uma forma alternativa de escrever a mesma coisa:

C# sintaxe de query
var nomesAtivos =
    from c in clientes
    where c.Ativo
    orderby c.TotalCompras descending
    select c.Nome;

7. Onde isso te leva

LINQ vai reaparecer constantemente — inclusive como a base do Entity Framework Core, que traduz exatamente essas mesmas expressões para SQL de verdade. Antes disso, o próximo capítulo cobre async/await: como escrever código que espera uma operação (como uma chamada ao banco) sem travar a aplicação inteira.

📌 Resumo do capítulo

  • LINQ é o equivalente C# de map/filter/reduce, com muito mais operadores (GroupBy, OrderBy, Any, All...).
  • A maioria dos operadores usa execução adiada — a query só roda de fato quando iterada ou materializada com .ToList()/.Count().
  • Tipos anônimos (new {'{'} ... {'}'}) criam formas de dado temporárias sem precisar declarar uma classe.
  • Existem duas sintaxes equivalentes: método (.Where().Select()) e query (from...where...select).

✏️ Praticando

  1. Com a lista de Cliente do exemplo, escreva uma query LINQ que devolve só os nomes dos clientes inativos.
  2. Com uma lista de Pedido (ClienteId, Valor, Data), agrupe por mês e calcule o total vendido em cada mês.
  3. Demonstre a pegadinha de execução adiada: crie uma query com .Where(), adicione um item à lista original depois, e mostre que o item novo aparece no resultado ao iterar.