Alocação não é defeito por si só
Objetos gerenciados são alocados no heap e liberados quando deixam de ser alcançáveis; o GC decide quando recuperar memória. Alocações pequenas e transitórias podem ser baratas. Pressão constante, objetos grandes retidos ou coletas frequentes podem aumentar pausas e consumo. Observe taxa de alocação, tamanho do heap e tempo em GC junto com latência e carga.
Comece com contadores ou um profiler, e não com a remoção indiscriminada de abstrações. O artigo de diagnóstico com ferramentas .NET apresenta um fluxo de investigação.
Procure trabalho e cópias evitáveis
Em código chamado muitas vezes, confira materializações repetidas, conversões de string, cópias de arrays e criação de coleções sem capacidade inicial conhecida. Faça uma alteração clara de cada vez. Um StringBuilder pode ajudar a montar texto em um loop com muitas partes, mas concatenar poucas strings pode ser mais simples e igualmente eficiente; compare o cenário real.
Em leituras de banco, projetar só os campos necessários também reduz dados transferidos e objetos materializados; veja o guia de EF Core e SQL. Evite enumerar a mesma sequência mais de uma vez se isso repetir trabalho ou consultas.
Reduza alocações somente em caminhos quentes
Quando medições confirmam que uma operação frequente cria pressão, considere reutilizar buffers ou evitar cópias. Span<T> e Memory<T> podem representar fatias de memória; APIs de pooling podem amortizar buffers temporários grandes. Essas ferramentas trazem regras de vida útil e ownership, portanto são inadequadas como otimização automática para todo método.
Antes de adicionar pooling, responda: o buffer é grande e frequentemente recriado? A retenção de memória será limitada? Ele pode conter dados sensíveis? Há um try/finally que garante devolução mesmo em exceção? Se não há respostas claras, prefira alocação simples e segura.
Cuidados com objetos grandes e referências longas
Objetos grandes podem ter custo de alocação e coleta diferente de objetos pequenos. Buffers retidos por singletons, caches sem limite ou closures e eventos que mantêm referências podem impedir que memória seja coletada. Procure crescimento contínuo e retenção em dumps ou profilers; tamanho alto do heap sozinho não comprova vazamento.
Não chame GC.Collect() para “resolver” consumo de memória em operação normal. Forçar coleta pode interromper trabalho e adiar o problema em vez de corrigir as referências que mantêm objetos vivos.
Meça alocações e valide impacto
Use [MemoryDiagnoser] no BenchmarkDotNet para comparar alocações em métodos controlados. Em uma aplicação real, compare heap, GC e latência sob carga parecida antes e depois. Uma redução de bytes por operação só importa se reduzir pressão ou melhorar metas do serviço sem criar complexidade desnecessária.
O guia de performance em .NET conecta microbenchmarks, diagnóstico e validação de ponta a ponta.