Regra principal: operadores LINQ como Where, Select, OrderBy sao lazy β€” nada executa ate voce enumerar (foreach, ToList, Count...). Isso e a deferred execution.

Pipeline de execucao LINQ

lazylazyexecuta!SourceList<T>Where()filtroSelect()projecaoToList()materializacaoCada operador e lazynada executa ate enumerar

O que e Deferred Execution?

Quando voce chama Where() ou Select(), nenhum dado e processado. Voce esta apenas definindo o pipeline. A execucao so acontece quando alguem enumera o resultado β€” via foreach, ToList(), Count(), etc.

// Deferred β€” nada executa ao definir o pipeline var nums = new List<int> { 1, 2, 3, 4 }; var query = nums.Where(n => n > 2); // deferred; nothing executed yet nums.Add(5); // modifica APOS criar a query Console.WriteLine(string.Join(",", query)); // prints 3,4,5 // O 5 aparece porque a query so executa AGORA β€” na enumeracao

Materializacao com ToList()

Materializadores como ToList(), ToArray(), ToHashSet() executam o pipeline imediatamente e armazenam o resultado. Mudancas na fonte apos a materializacao nao afetam o snapshot.

// Materialization β€” snapshot vs live view var nums = new List<int> { 1, 2, 3, 4 }; var live = nums.Where(n => n % 2 == 0); // deferred (live view) var frozen = nums.Where(n => n % 2 == 0).ToList(); // executed now (snapshot) nums.Add(6); Console.WriteLine(string.Join(",", live)); // 2,4,6 (ve os novos dados) Console.WriteLine(string.Join(",", frozen)); // 2,4 (snapshot tirado antes)

Simulador: Deferred vs Immediate

A diferenca esta no codigo: deferred monta um pipeline e so executa no final. Immediate materializa a cada passo.

Codigo Deferred:
var source = new List<int> { 1, 2, 3, 4, 5 }; // Linha 1: NADA executa β€” retorna IEnumerable<int> (lazy) var query = source .Where(n => n > 2) // nao filtra ainda .Select(n => n * 10); // nao projeta ainda // "query" e so uma receita β€” zero processamento ate aqui // source poderia mudar e o resultado refletiria // Linha 2: AGORA executa tudo de uma vez var result = query.ToList(); // [30, 40, 50]
1
Source (List<int>)
Dados na memoria: [1, 2, 3, 4, 5]
aguardando
2
Where(n => n > 2)
Filtra elementos maiores que 2
aguardando
3
Select(n => n * 10)
Projeta multiplicando por 10
aguardando
4
ToList()
Materializa β€” executa o pipeline agora
aguardando

Perigo: enumeracao multipla

Cada vez que voce enumera um IEnumerable, o pipeline re-executa. Se a fonte e cara (query ao banco, API call), voce paga o custo multiplas vezes.

// Perigo: enumeracao multipla re-executa o pipeline IEnumerable<int> Expensive() { Console.WriteLine("Computing..."); return Enumerable.Range(1, 3).Select(n => { Console.WriteLine($"n={n}"); return n; }); } var pipeline = Expensive().Where(n => n > 1); // no work yet Console.WriteLine(pipeline.Sum()); // triggers enumeration #1 β€” "Computing..." + logs Console.WriteLine(pipeline.Count()); // triggers enumeration #2 β€” "Computing..." + logs AGAIN // Solucao: materializar uma vez var list = pipeline.ToList(); // executa 1x Console.WriteLine(list.Sum()); // usa lista em memoria Console.WriteLine(list.Count()); // sem re-execucao
Qual e o perigo de enumerar multiplas vezes (multiple enumeration)?
Cada enumeracao re-executa todo o pipeline. Se a fonte e uma query de banco, voce faz N queries ao inves de 1. Se a fonte produz side-effects (logging, timers), eles se repetem. A solucao e materializar com ToList() e usar a lista materializada para Sum(), Count(), etc. Alem disso, se os dados mudarem entre enumeracoes, os resultados serao inconsistentes.

Metodos que materializam

Retornam colecao

ToList()ToListAsync()ToArray()ToHashSet()ToDictionary()ToLookup()

Retornam elemento unico

First()FirstOrDefault()Single()SingleOrDefault()Last()ElementAt()

Retornam escalar

Count()LongCount()Any()All()Sum()Average()Min()Max()Contains()

Iteracao implicita

foreach