Lifetimes: Singleton, Scoped e Transient
ResumoO lifetime define por quanto tempo o container reutiliza uma instância:
Singleton(uma para a app inteira),Scoped(uma por requisição) eTransient(uma nova a cada injeção). Escolher errado causa bugs sutis.
1. Objetivos de Aprendizagem#
- Diferenciar os três lifetimes.
- Escolher o lifetime adequado a cada serviço.
- Evitar a armadilha do captive dependency.
2. Pré-requisitos#
- Container de DI (tópico 12.2).
3. Conceito#
O diagrama abaixo resume o fluxo principal.
AddSingleton: uma única instância para toda a aplicação. Boa para serviços sem estado por requisição (cache, configuração). Precisa ser thread-safe.AddScoped: uma instância por escopo — no ASP.NET Core, por requisição HTTP. Ideal para oDbContextdo EF Core e serviços/repositórios.AddTransient: uma nova instância a cada vez que é solicitada. Para serviços leves e sem estado.
O porquê: compartilhar estado no lifetime errado leva a dados vazando entre requisições (singleton com estado) ou a inconsistências.
4. Mão na Massa#
4.1. Setup#
dotnet new console -n Catalog.Lifetimes
cd Catalog.Lifetimes
dotnet add package Microsoft.Extensions.DependencyInjection
4.2. Implementação Passo a Passo#
- Registrar com lifetimes diferentes:
using Microsoft.Extensions.DependencyInjection;
public interface IServico { Guid Id { get; } }
public class Servico : IServico { public Guid Id { get; } = Guid.NewGuid(); }
var services = new ServiceCollection();
services.AddTransient<IServico, Servico>(); // troque para Scoped/Singleton e compare
using var provider = services.BuildServiceProvider();
var a = provider.GetRequiredService<IServico>();
var b = provider.GetRequiredService<IServico>();
Console.WriteLine(a.Id == b.Id); // Transient: False | Singleton: True
- Escopos (simulando requisições):
using (var escopo = provider.CreateScope())
{
var s1 = escopo.ServiceProvider.GetRequiredService<IServico>();
var s2 = escopo.ServiceProvider.GetRequiredService<IServico>();
// Scoped: s1.Id == s2.Id dentro do mesmo escopo
}
4.3. Executando#
dotnet run
5. Exemplo Completo#
Trocar o registro entre Transient, Scoped e Singleton e comparar os Id das instâncias demonstra na prática quando o container reaproveita ou recria o objeto.
6. Boas Práticas e Armadilhas#
| Serviço | Lifetime recomendado |
|---|---|
| DbContext (EF Core), repositórios, serviços de negócio | Scoped |
| Cache em memória, opções, factories sem estado | Singleton (thread-safe) |
| Serviços leves e sem estado | Transient |
Atençãoinjetar um serviço Scoped (como o
DbContext) dentro de um Singleton o "prende" para sempre, quebrando o comportamento por requisição e podendo causar erros de concorrência. Nunca injete um lifetime mais curto dentro de um mais longo.
7. Segurança e Produção#
- Singletons com estado mutável compartilhado entre requisições são fonte comum de race conditions e vazamento de dados entre usuários. Mantenha singletons sem estado ou thread-safe.
- O ASP.NET Core detecta algumas violações de escopo na inicialização quando a validação de escopos está ativa (padrão em Development).
8. Exercícios#
- Fácil: registre um serviço como Transient e Singleton e compare os
Id. - Médio: demonstre que dois
GetRequiredServiceno mesmo escopo (Scoped) retornam a mesma instância. - Desafio: explique por que injetar um Scoped em um Singleton é problemático e proponha uma solução.
9. Resumo#
Singleton = uma por app; Scoped = uma por requisição; Transient = uma por injeção. Use Scoped para DbContext/serviços, Singleton (sem estado) para cache/config, Transient para serviços leves. Cuidado com captive dependency.
10. Próximos Passos#
Com DI dominada, a seguir: configuração e options — muitas vezes injetadas via DI.
11. Referências#
- Microsoft Learn — Tempos de vida de serviços
- Microsoft Learn — Diretrizes de injeção de dependência
- Microsoft Learn — Tempo de vida e registro do DbContext