Какие типы жизненного цикла сервисов существуют в ASP.NET Core?

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

Ответ

В ASP.NET Core Dependency Injection (DI) контейнер управляет временем жизни сервисов. Существует три основных типа (Lifetime), которые определяют, как и когда создается экземпляр сервиса:

Тип Время жизни Регистрация Типичное использование
Transient Создается каждый раз при запросе из контейнера. AddTransient<TService, TImplementation>() Легковесные, статусные сервисы (например, Random, IStringLocalizer). Не должны хранить состояние между вызовами.
Scoped Создается один раз на область видимости (scope). Для веб-приложения — один на HTTP-запрос. AddScoped<TService, TImplementation>() Сервисы, работающие в контексте одного запроса: DbContext (Entity Framework Core), репозитории, сервисы бизнес-логики.
Singleton Создается один раз на все время работы приложения. AddSingleton<TService, TImplementation>() Сервисы с общим состоянием или дорогие в создании: кэши (IMemoryCache), клиенты (HttpClient с IHttpClientFactory), конфигурация.

Критические моменты и best practices:

  1. Зависимости между сервисами с разным временем жизни:

    • Singleton НИКОГДА не должен зависеть от Scoped или Transient сервисов. Это приведет к captive dependency — Scoped-сервис, захваченный Singleton-ом, будет жить вечно, что может вызвать утечку памяти (например, DbContext будет кэшировать данные и не освобождать соединения).
      // НЕПРАВИЛЬНО: Singleton зависит от Scoped.
      public class BadCacheService
      {
      private readonly AppDbContext _context; // Scoped!
      public BadCacheService(AppDbContext context) => _context = context; // Ошибка!
      }
  2. Работа с Scoped сервисами вне HTTP-запроса (в фоновых задачах, через IHostedService):

    • Необходимо вручную создать область видимости с помощью IServiceScopeFactory.

      public class BackgroundWorker : IHostedService
      {
      private readonly IServiceScopeFactory _scopeFactory;
      public BackgroundWorker(IServiceScopeFactory scopeFactory) => _scopeFactory = scopeFactory;
      
      public async Task DoWorkAsync()
      {
          // Создаем новую область видимости
          using (var scope = _scopeFactory.CreateScope())
          {
              // Получаем Scoped-сервис из новой области
              var scopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>();
              await scopedService.ProcessDataAsync();
          } // Здесь scoped-сервисы будут корректно удалены.
      }
      }

Как выбрать? Всегда начинайте с Scoped для сервисов с логикой запроса. Используйте Transient для простых, не имеющих состояния утилит. Singleton — только для сервисов с общим состоянием, доступ к которому должен быть синхронизирован.