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# — 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 foreach apenas 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.

    E estamos conversados...  

    "Ai dos que ao mal chamam bem, e ao bem mal; que fazem das trevas luz, e da luz trevas; e fazem do amargo doce, e do doce amargo!" Isaias 5:20

    Referências:


    José Carlos Macoratti