Já foi vítima de uma instrução LINQ lenta ?

Neste artigo veremos como não abusar da LINQ e fugir de problemas recorrentes.
A LINQ é um dos recursos mais elegantes do C#↳C#68 conteúdosNovidades do C# 8.0: Default Interface MembersDev (Back & Front) · jun 2019C# 8.0 – Conheça suas novas funcionalidadesDev (Back & Front) · mai 2019Novidades do C# 8.0: Switch ExpressionsDev (Back & Front) · mar 2019Ver tudo em Dev (Back & Front) → — compacto, expressivo e limpo. Mas, em sistemas de alto tráfego ou loops críticos, ela pode destruir silenciosamente o desempenho das suas consultas.
Muitos desenvolvedores amam a LINQ por sua legibilidade, mas ela tem um custo: alocações de delegates, overhead de enumeradores e pressão desnecessária na memória.
A boa notícia ? Você não precisa abandoná-la.
Só precisa usá-la com consciência. Vamos ver isso na prática…
1. Não Chame .ToList() Mais do que Você Precisa
Isso é mais comum do que deveria:
var resultado = ObterUsuarios().Where(u => u.EstaAtivo);
if (resultado.Any()) { /* lógica */ }
foreach (var usuario in resultado) { /* código */ }
Se ObterUsuarios() retorna um IEnumerable, a consulta possui execução adiada. Nesse caso , ela será enumerada duas vezes.
1- Durante o .Any que percorre a sequência até encontrar o primeiro elemento
2- Durante o foreach que percorre a sequência novamente
Melhor alternativa:
var usuariosAtivos = ObterUsuarios().Where(u => u.EstaAtivo).ToList();
if (usuariosAtivos .Count > 0) { /* lógica */ }
foreach(var usuario in usuariosAtivos ){/* código*/ }
Aqui o ToList() força a execução da consulta imediatamente e materializa o resultado em uma List<T>
✔ Agora a consulta original é enumerada apenas uma vez, durante o .ToList(). Depois disso , Count e foreach trabalham sobre a lista já materializada.
2. Substitua LINQ por foreach em Loops Críticos
Códigos LINQ como este são bonitos:
var total = usuarios.Where(u => u.EstaVerificado).Sum(u => u.Pontuacao);
Mas, se isso roda milhares de vezes por segundo, um simples foreach é pode sermais rápido:
int total = 0;
foreach (var usuario in usuarios)
{
if (usuario.EstaVerificado)
total += usuario.Pontuacao;
}
✔ Evite alocações de lambdas, overhead de enumeradores e abstrações extra
Em loops extremamente frequentes e sensíveis à performance, um foreach pode ser mais eficiente que LINQ, reduzindo o overhead de delegates e da abstração de enumeração.
3. Não Use .Count() Quando .Any() Resolve
Outro erro comum:
if (produtos.Count() > 0) // Pode percorrer toda a sequência!
Prefira:
if (produtos.Any()) // Para no primeiro item encontrado!
✔ Mais rápido, mais leve e sempre preferível quando você só quer saber se algo existe
Nota: O Count() pode precisar percorrer a sequência quando a fonte não fornece a quantidade diretamente. Quando você só precisa saber se existe algum elemento, Any() expressa melhor a intenção e pode parar no primeiro elemento.
4. Evite Cadeias Complexas de LINQ
Códigos como este parecem elegantes:
var pontuacoesAltas = usuarios
.Where(u => u.EstaAtivo)
.Select(u => u.Pontuacao)
.Where(pontuacao => pontuacao > 80)
.ToList();
Neste código, o pipeline é avaliado quando o ToList() é executado e os elementos passam pela cadeia de operações durante essa enumeração.
Assim, uma cadeia LINQ pode introduzir iteradores e chamadas adicionais em relação a um loop manual. Em código extremamente sensível à performance, um foreach pode reduzir esse overhead e dar ao compilador/runtime mais oportunidades de otimização.
var pontuacoesAltas = new List<int>();
foreach (var usuario in usuarios)
{
if (usuario.EstaAtivo && usuario.Pontuacao > 80)
pontuacoesAltas.Add(usuario.Pontuacao);
}
✔ Essencial em sistemas de tempo real, servidores de jogos ou processamento pesado.
5. Não Filtre os Mesmos Dados Várias Vezes
Já viu isso em código de produção?
var ativos = usuarios.Where(u => u.EstaAtivo);
var contagem = ativos.Count(); // Enumeração 1
var primeiro = ativos.FirstOrDefault(); // Enumeração 2
Isso gera duas enumerações desnecessárias. O Count() enumera ativos para obter a quantidade e FirstOrDefault() faz uma nova enumeração para obter o primeiro elemento.
Se a consulta for custosa — por exemplo, envolver processamento adicional ou acesso a uma fonte externa — essa repetição pode ser desnecessária.
Melhor:
var ativos = usuarios.Where(u => u.EstaAtivo).ToList();
var contagem = ativos.Count; // Já está em memória
var primeiro = ativos.FirstOrDefault(); // Sem nova enumeração
Aqui ToList() executa a consulta uma vez e materializa os resultados. Depois, Count usa a propriedade da lista e FirstOrDefault() acessa a lista sem precisar executar novamente o Where.
✔ Itera apenas uma vez e reutiliza os resultado
Nota: usar ToList() não é automaticamente SEMPRE melhor. Você está trocando duas enumerações por uma enumeração + materialização em memória. Isso vale a pena quando você realmente precisa reutilizar os resultados.
Resumo
- Evite enumerar repetidamente a mesma consulta quando uma única avaliação puder ser reutilizada.
- Use
.Any()quando quiser apenas saber se existe algum elemento. - Em trechos realmente críticos de desempenho, considere substituir LINQ por
foreach. - Antes de otimizar, meça: não troque LINQ por
foreachapenas por suposição. - Priorize clareza, mas conheça o custo das abstrações usadas em código de alta frequência.
Na próxima vez que você analisar seu app e se perguntar “De onde vêm esses picos de memória?”, verifique as suas cadeias LINQ.
A LINQ tem um custo e, em código crítico, esse custo pode importar.







