← Назад к вопросам

Что такое Transient?

1.0 Junior🔥 201 комментариев
#Основы C# и .NET

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое Transient в контексте разработки на C#?

В разработке на C# (особенно с использованием Dependency Injection в ASP.NET Core) термин Transient относится к одному из трех основных времен жизни служб (service lifetimes), наряду с Scoped и Singleton. Это ключевая концепция управления зависимостями и временем жизни объектов.

Определение и семантика

Transient (в переводе — "мимолётный", "временный") означает, что каждый раз, когда контейнер внедрения зависимостей (DI-контейнер) получает запрос на разрешение (resolve) данной службы, создаётся новый экземпляр объекта. Каждый потребитель (например, контроллер, сервис, middleware) получает свой собственный, уникальный экземпляр. Этот экземпляр не разделяется между разными потребителями и даже между несколькими запросами одного и того же потребителя в рамках одного запроса.

Как зарегистрировать Transient-службу?

В ASP.NET Core регистрация происходит в методе ConfigureServices класса Startup или с использованием минимальных API.

// Регистрация Transient службы в ASP.NET Core 6+ (минимальные API)
var builder = WebApplication.CreateBuilder(args);

// Вариант 1: Регистрация типа с его реализацией
builder.Services.AddTransient<IMyService, MyService>();

// Вариант 2: Регистрация конкретного типа (без интерфейса)
builder.Services.AddTransient<MyService>();

// Вариант 3: Регистрация с использованием фабрики для сложной логики создания
builder.Services.AddTransient<IMyService>(serviceProvider =>
{
    var logger = serviceProvider.GetRequiredService<ILogger<MyService>>();
    var config = serviceProvider.GetRequiredService<IConfiguration>();
    return new MyService(logger, config.GetValue<string>("SomeSetting"));
});

Поведение и практические последствия

  • Создание экземпляров: Новый объект создаётся для каждого внедрения. Если одна служба A внедряет Transient службу B в 5 разных местах своего конструктора или методов, будет создано 5 отдельных экземпляров B.
  • Управление памятью: Поскольку экземпляры создаются часто, это может привести к повышенному расходу памяти и нагрузки на сборщик мусора (GC), если служба тяжеловесная.
  • Потокобезопасность: Так как экземпляры не разделяются, проблемы с состоянием, общим для нескольких потоков, минимизированы на уровне экземпляра. Однако если служба использует статические поля или внешние ресурсы (файлы, БД), потокобезопасность должна обеспечиваться отдельно.
  • Освобождение ресурсов (Dispose): Для служб, реализующих IDisposable, DI-контейнер автоматически вызовет метод Dispose() в конце области видимости запроса (scope), в котором они были созданы.

Пример, иллюстрирующий поведение

Рассмотрим простой пример, который наглядно демонстрирует разницу:

public interface ICounter
{
    int GetNext();
}

public class TransientCounter : ICounter
{
    private int _count = 0;
    public int GetNext() => ++_count;
}

// В контроллере или другом сервисе:
public class MyController : ControllerBase
{
    private readonly ICounter _counter1;
    private readonly ICounter _counter2;

    // Оба параметра - один и тот же тип ICounter, зарегистрированный как Transient
    public MyController(ICounter counter1, ICounter counter2)
    {
        _counter1 = counter1;
        _counter2 = counter2;
    }

    public IActionResult Get()
    {
        var result1 = _counter1.GetNext(); // Вернёт 1
        var result2 = _counter1.GetNext(); // Вернёт 2 (тот же экземпляр)
        var result3 = _counter2.GetNext(); // Вернёт 1! (это совершенно другой экземпляр)

        return Ok(new { result1, result2, result3 });
    }
}

Когда следует использовать Transient?

Используйте времен жизни Transient для служб, которые:

  1. Не имеют состояния (stateless) или их состояние не должно сохраняться между вызовами.
  2. Лёгкие и недорогие в создании (не осуществляют тяжелых операций инициализации, не содержат больших объектов в памяти).
  3. Не являются потокобезопасными и не должны использоваться несколькими потребителями одновременно.
  4. Требуют изоляции (например, сервисы валидации, преобразователи данных (mappers), простые фабрики).

Сравнение с Scoped и Singleton

  • Transient vs Singleton: Singleton создаётся один раз на всё время жизни приложения и используется всеми запросами и потребителями. Transient создаётся каждый раз заново.
  • Transient vs Scoped: Scoped-служба создаётся один раз в пределах области видимости (обычно — один HTTP-запрос). Все потребители внутри этого запроса получают один и тот же экземпляр. Transient-служба создаётся заново для каждого отдельного потребителя, даже внутри одного запроса.

Важные предостережения

  1. Цепочки зависимостей: Если Singleton-служба зависит от Transient-службы, эта Transient-служба также становится де-факто Singleton, потому что она будет инжектирована в Singleton лишь один раз при его создании. Это может быть неочевидно и привести к ошибкам.
  2. Производительность: Не используйте Transient для служб с дорогой инициализацией (например, устанавливающих сетевое подключение). Предпочитайте Singleton или Scoped, если это допустимо по семантике работы службы.
  3. Захват зависимостей (Captive Dependency): Это ситуация, когда служба с более длительным временем жизни (например, Singleton) удерживает ссылку на службу с более коротким временем жизни (например, Transient, который зависит от Scoped). Это может привести к утечкам памяти или неправильному поведению.

Вывод: Выбор Transient — это осознанное решение о том, что каждый потребитель должен работать с изолированным, независимым экземпляром службы. Это обеспечивает максимальную безопасность и предсказуемость на уровне экземпляра, но требует внимания к производительности и корректности построения графа зависимостей.