В чём разница между Transient, Scoped и Singleton в ASP.NET Core Dependency Injection?

«В чём разница между Transient, Scoped и Singleton в ASP.NET Core Dependency Injection?» — вопрос из категории ASP.NET Core, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Это три основных времени жизни (lifetime) сервисов, регистрируемых в DI-контейнере ASP.NET Core. Выбор влияет на производительность, потребление памяти и корректность работы приложения.

1. Transient (AddTransient)

  • Поведение: Создаётся новый экземпляр сервиса каждый раз, когда он запрашивается из контейнера.
  • Использование: Для лёгких, статусных (stateful) или дорогих в создании сервисов, которые не должны разделяться.
  • Пример:
    services.AddTransient<IMyService, MyService>();
    // В контроллере дважды запрошен IMyService:
    var service1 = scope.ServiceProvider.GetService<IMyService>(); // Новый экземпляр
    var service2 = scope.ServiceProvider.GetService<IMyService>(); // Ещё один новый экземпляр

2. Scoped (AddScoped)

  • Поведение: Создаётся один экземпляр на область видимости (scope). В веб-приложении область видимости по умолчанию соответствует одному HTTP-запросу.
  • Использование: Идеально для сервисов, которым необходим контекст в рамках одной операции (например, DbContext Entity Framework Core).
  • Пример:
    services.AddScoped<IMyRepository, MyRepository>();
    // В рамках одного запроса несколько классов получат один и тот же экземпляр репозитория.

3. Singleton (AddSingleton)

  • Поведение: Создаётся один экземпляр на всё время жизни приложения (с момента первого разрешения).
  • Использование: Для статусных (stateless), потокобезопасных сервисов, таких как кэш в памяти, логгеры или клиенты для внешних API.
  • Пример:
    services.AddSingleton<IMemoryCache, MemoryCache>();

Критические правила и best practices:

  • Потокобезопасность: Singleton-сервисы должны быть потокобезопасными, так как к ним возможен одновременный доступ из множества потоков.
  • Зависимости (Captive Dependency): Никогда не инжектируйте Scoped-сервис в Singleton. Singleton «захватит» Scoped-сервис, и тот будет жить вечно, что может привести к утечке памяти (например, DbContext не будет освобождать соединения).
  • Производительность: Transient для тяжёлых объектов может создать нагрузку, а Singleton для объектов с состоянием — ошибки.