Introdução: Por que a performance é importante desde as primeiras linhas de código
Cada desenvolvedor, mesmo iniciante, enfrenta situações em que uma aplicação simples começa a ficar lenta, consumir muita memória ou demorar para processar requisições. A otimização de performance de aplicações C# não é mágica nem privilégio de desenvolvedores seniores. É um conjunto de regras práticas e ferramentas que podem ser aprendidas em qualquer etapa do aprendizado.
Neste artigo, vamos analisar os principais aspectos da otimização de código em C# (.NET). Você aprenderá como evitar erros típicos de iniciantes, acelerar o trabalho com memória, strings e coleções, além de usar corretamente o multithreading. O material é voltado para desenvolvedores que já conhecem os fundamentos da sintaxe C# e desejam escrever código mais eficiente.
1. Trabalhando com strings: por que String é ruim e StringBuilder é bom
Um dos problemas de performance mais comuns em C# é o trabalho incorreto com strings. Strings no .NET são imutáveis (immutable). Isso significa que qualquer operação de concatenação cria um objeto novo de string na memória, enquanto o objeto antigo fica para o garbage collector.
Problema: concatenação em loop
Considere um exemplo ruim:
string result = "";for (int i = 0; i < 1000; i++){ result += i.ToString() + " ";}// Aqui são criadas 1000 strings intermediárias!Cada iteração do loop cria uma nova string. Para 1000 iterações — são 1000 alocações de memória. Se houver 100.000 iterações, a aplicação pode "travar" ou causar OutOfMemoryException.
Solução: use StringBuilder
A classe StringBuilder trabalha com um buffer de caracteres mutável. Ela não cria novos objetos a cada adição de texto.
using System.Text;
StringBuilder sb = new StringBuilder();for (int i = 0; i < 1000; i++){ sb.Append(i); sb.Append(" ");}string result = sb.ToString();Dica: Sempre use StringBuilder se você for realizar mais de 3-5 operações de concatenação, especialmente dentro de loops. Para a simples junção de algumas strings (ex.: string s = a + b + c;), o compilador C# já otimiza isso para uma única chamada a string.Concat, então StringBuilder não é necessário aqui.
2. Otimizando LINQ: escolhendo os métodos corretos
LINQ (Language Integrated Query) é uma ferramenta poderosa, mas seu uso incorreto pode tornar a aplicação muito lenta. O principal problema é a enumeração múltipla de coleções.
Exemplo de uma consulta LINQ "cara"
var numbers = Enumerable.Range(1, 1000000);var countEven = numbers.Where(n => n % 2 == 0).Count();// Aqui Where() cria um iterador, Count() o percorre.// Tudo bem se a coleção for percorrida uma vez.O erro ocorre quando você chama várias operações terminais (Count, ToList, First, Any) na mesma expressão LINQ:
var query = numbers.Where(n => n % 2 == 0);int count1 = query.Count(); // Primeira passagemint count2 = query.Count(); // Segunda passagem!// A coleção é percorrida duas vezes.Cada chamada a Count() ou ToList() executa a filtragem novamente. Se você tiver uma filtragem complexa ou a fonte de dados for um banco de dados (Entity Framework), isso resulta em consultas duplicadas.
Abordagem correta: materialização
Se você precisar usar o resultado de uma consulta várias vezes, salve-o em uma lista ou array:
var query = numbers.Where(n => n % 2 == 0).ToList(); // Uma passagemint count1 = query.Count;int count2 = query.Count;Dica: Use .ToList() ou .ToArray() imediatamente após a filtragem se você for chamar vários métodos (Count, First, Sum). Para uma única chamada, deixe