Pular para o conteúdo
.NETLifetimes: Singleton, Scoped e Transient
Módulo 02Fundamentos de Aplicação

Lifetimes: Singleton, Scoped e Transient

intermediario 26 min de leitura·Atualizado · .NET 9
Resumo

O lifetime define por quanto tempo o container reutiliza uma instância: Singleton (uma para a app inteira), Scoped (uma por requisição) e Transient (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.

Carregando diagrama…
  • 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 o DbContext do 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#

Shell
dotnet new console -n Catalog.Lifetimes
cd Catalog.Lifetimes
dotnet add package Microsoft.Extensions.DependencyInjection

4.2. Implementação Passo a Passo#

  1. Registrar com lifetimes diferentes:
C#
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
  1. Escopos (simulando requisições):
C#
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#

Shell
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çoLifetime recomendado
DbContext (EF Core), repositórios, serviços de negócioScoped
Cache em memória, opções, factories sem estadoSingleton (thread-safe)
Serviços leves e sem estadoTransient
Atenção

injetar 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 GetRequiredService no 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#

12-03 — Lifetimes: Singleton, Scoped e Transient | Curso ASP.NET Core