Ответ
AddSingleton — это метод расширения для регистрации сервиса с одиночным (Singleton) временем жизни в контейнере внедрения зависимостей (DI) ASP.NET Core.
Поведение: Создается ровно один экземпляр сервиса на все время работы приложения. Этот экземпляр используется для всех запросов и всех пользователей.
Регистрация сервиса:
public void ConfigureServices(IServiceCollection services)
{
// Регистрация реализации как синглтона
services.AddSingleton<IMyService, MyService>();
// Регистрация существующего экземпляра как синглтона
var instance = new MyService();
services.AddSingleton<IMyService>(instance);
// Регистрация типа, который будет использоваться и как сервис, и как реализация
services.AddSingleton<MyService>();
}
Когда использовать AddSingleton:
- Сервисы без состояния (Stateless): Кэши (
IMemoryCache), клиенты для внешних API (если потокобезопасны), логгеры (фабрика логгеров сама управляет жизненным циклом). - Дорогие в создании объекты: Объекты, инициализация которых требует больших ресурсов (например, загрузка конфигурации, установка соединения).
- Хранилища данных в памяти, которые должны быть доступны всем пользователям (например, in-memory репозиторий для демо).
Критически важные предостережения:
- Потокобезопасность (Thread Safety): Поскольку один экземпляр используется одновременно многими запросами, ваша реализация синглтона должна быть потокобезопасной. Используйте конструкции
lock,SemaphoreSlimили concurrent-коллекции (ConcurrentDictionary). - Зависимости: Синглтон не должен зависеть от сервисов с временем жизни
Scoped(например,DbContext), так как это может привести к утечке памяти или неправильному использованию ресурсов (Scoped-сервис превратится в синглтон). - Disposable: Если синглтон реализует
IDisposable, контейнер НЕ вызоветDispose()автоматически. Утилизацию нужно управлять вручную, например, черезIApplicationLifetime.ApplicationStopping.
Ответ 18+ 🔞
А, ну это про AddSingleton! Давай разберём, что это за зверь такой, а то народ часто путается, а потом сидят и чешут репу.
Вот представь себе, что у тебя есть какой-нибудь здоровенный, навороченный кофейный аппарат на кухне в офисе. Он один на всех, стоит себе в углу, гремит. Кто хочет кофе — идёт и льёт из него. Неважно, Вася из бухгалтерии пришёл или Катя из отдела кадров — аппарат один и тот же. Вот AddSingleton — это ровно про это.
Как оно работает, если по-простому: Ты регистрируешь сервис как синглтон — и контейнер DI создаёт его один раз, на старте приложения, и больше не отсоебляется. Этот единственный экземпляр потом тычут всем подряд, кто его запрашивает. Все запросы, все пользователи — все пьют из одной чашки, прости господи.
Как это в коде выглядит:
public void ConfigureServices(IServiceCollection services)
{
// Самый частый вариант — сказали тип интерфейса и тип реализации.
// Создаст один экземпляр MyService и будет всем его подсовывать.
services.AddSingleton<IMyService, MyService>();
// Вариант для крутых — если у тебя уже есть готовый, настроенный объект.
// Держи, говорит, вот этот инстанс, и больше никого не трогай.
var preBuiltInstance = new MyService();
services.AddSingleton<IMyService>(preBuiltInstance);
// Ну или если твой сервис — он сам себе и интерфейс, и реализация.
// Регистрируешь просто тип.
services.AddSingleton<MyService>();
}
А когда это вообще нужно?
- Объекты, которые создавать — овердохуища ресурсов. Ну, например, тяжёлый кэш в памяти (
IMemoryCache), какой-нибудь сконфигурированный HTTP-клиент для внешнего API, или парсер конфигов, который пол-гигабайта JSON'ов жуёт при старте. Создали один раз — и поехали. - Сервисы без состояния (stateless). Им похуй, кто и когда их использует, им не нужно помнить контекст запроса. Главное — чтобы они были потокобезопасными, об этом ниже.
- Какие-нибудь глобальные хранилища данных в памяти для демо-приложений. Типа in-memory репозитория, где список пользователей лежит в обычном
List. Все видят один и тот же список.
А теперь, внимание, самое важное — подводные ебеня!
-
Потокобезопасность — это твоя головная боль. Поскольку один объект будут дёргать одновременно кучу потоков (каждый запрос — свой поток), ты должен сделать так, чтобы они друг другу не наступили на мозги. Если внутри синглтона есть общее состояние (тот же
List), обязательно используйlock,SemaphoreSlimили thread-safe коллекции вродеConcurrentDictionary. Иначе будет весело: данные поплывут, исключения посыплются, а ты будешь искать, кто тут мудак. А мудак — это ты, потому что не предусмотрел. -
Зависимости — смотри в оба. Если твой синглтон вдруг возьмёт и захочет зависеть от сервиса с временем жизни
Scoped(например, отDbContext), то жди беды. Scoped-сервис создаётся на время запроса, а потом должен умирать. Но если синглтон за него ухватится, то он не умрёт, а превратится в такого же синглтона-зомби. Это прямая дорога к утечке памяти и к тому, что данные между пользователями начнут смешиваться. Пипец, короче. -
Если твой синглтон реализует
IDisposable— контейнер его НЕ убьёт. Да-да, он создаст его один раз при старте, а когда приложение будет завершаться — просто оставит его догнивать. Если нужно корректно освободить ресурсы (закрыть файл, сетевое соединение), тебе нужно подписаться на событие завершения приложения (IApplicationLifetime.ApplicationStopping) и там вручную вызватьDispose(). Иначе получишь тихую, но противную утечку.
Итог: AddSingleton — мощный инструмент, но как молоток. Можно гвоздь забить, а можно себе по пальцам въебать. Используй для действительно глобальных, недешёвых и потокобезопасных вещей. А если сомневаешься — лучше начни со Scoped, меньше шансов что-то запороть.