Ответ
Да, можно. Стандартный контейнер Microsoft.Extensions.DependencyInjection поддерживает эту возможность.
Существует несколько основных сценариев:
1. Регистрация и разрешение последней зарегистрированной реализации (по умолчанию)
При прямом запросе IService будет возвращена последняя зарегистрированная реализация.
// Регистрация в Program.cs
builder.Services.AddTransient<INotifier, EmailNotifier>();
builder.Services.AddTransient<INotifier, SmsNotifier>(); // Этот будет использован по умолчанию
// Внедрение и использование (получим SmsNotifier)
public class OrderService
{
private readonly INotifier _notifier;
public OrderService(INotifier notifier) // Внедрится SmsNotifier
{
_notifier = notifier;
}
}
2. Разрешение ВСЕХ зарегистрированных реализаций
Для реализации паттерна «Стратегия» или цепочки ответственности можно запросить IEnumerable<T>.
// Регистрация нескольких валидаторов
builder.Services.AddTransient<IOrderValidator, StockValidator>();
builder.Services.AddTransient<IOrderValidator, PaymentValidator>();
builder.Services.AddTransient<IOrderValidator, FraudValidator>();
// Внедрение коллекции всех валидаторов
public class OrderProcessor
{
private readonly IEnumerable<IOrderValidator> _validators;
public OrderProcessor(IEnumerable<IOrderValidator> validators)
{
_validators = validators; // Получим массив из 3-х валидаторов
}
public bool Validate(Order order)
{
// Последовательно выполняем все валидации
return _validators.All(v => v.Validate(order));
}
}
3. Именованное или типизированное разрешение (требует кастомного подхода)
Стандартный контейнер не поддерживает именованные регистрации «из коробки». Этого можно достичь, используя фабрику:
// Регистрация с ключом
builder.Services.AddTransient<EmailNotifier>();
builder.Services.AddTransient<SmsNotifier>();
builder.Services.AddTransient<Func<string, INotifier>>(serviceProvider => key =>
{
return key switch
{
"email" => serviceProvider.GetRequiredService<EmailNotifier>(),
"sms" => serviceProvider.GetRequiredService<SmsNotifier>(),
_ => throw new KeyNotFoundException()
};
});
// Использование
public class NotificationService
{
private readonly Func<string, INotifier> _notifierFactory;
public void Notify(string channel)
{
var notifier = _notifierFactory(channel); // Получаем нужную реализацию по ключу
notifier.Send("Hello");
}
}
Для сложных сценариев можно рассмотреть сторонние контейнеры (Autofac, Lamar), которые имеют встроенную поддержку именованных регистраций.
Ответ 18+ 🔞
Так, слушай сюда, про внедрение зависимостей в .NET. Ну, блядь, народ постоянно спрашивает, можно ли зарегить несколько сервисов под одним интерфейсом и как потом это чудо доставать. Отвечаю — да, конечно, можно, ёпта! Контейнер Microsoft.Extensions.DependencyInjection это умеет, хоть и не без своих, сука, приколов.
Вот смотри, основные сценарии, чтобы не ебал мозги:
1. Последний зарегистрированный — он же главный (де-факто)
По умолчанию, если ты запросишь просто интерфейс, тебе впендюрят последнюю зарегистрированную реализацию. Как в очереди за водкой — кто последний встал, того и талон.
// Регистрируем в Program.cs
builder.Services.AddTransient<INotifier, EmailNotifier>();
builder.Services.AddTransient<INotifier, SmsNotifier>(); // Вот этот красавец и будет главным!
// А потом в каком-нибудь сервисе...
public class OrderService
{
private readonly INotifier _notifier;
public OrderService(INotifier notifier) // Сюда придёт SmsNotifier, ясненько?
{
_notifier = notifier;
}
}
2. Хочешь всех сразу? Получи IEnumerable<T>!
Бывает, надо не одного, а всех, кто реализует интерфейс. Например, для паттерна "Цепочка ответственности" или кучи валидаторов. Тогда просто запрашиваешь коллекцию — и контейнер, такой добрый, отдаёт тебе всех.
// Нарегали кучу валидаторов
builder.Services.AddTransient<IOrderValidator, StockValidator>();
builder.Services.AddTransient<IOrderValidator, PaymentValidator>();
builder.Services.AddTransient<IOrderValidator, FraudValidator>();
// А в процессоре заказа...
public class OrderProcessor
{
private readonly IEnumerable<IOrderValidator> _validators; // Все трое тут как тут!
public OrderProcessor(IEnumerable<IOrderValidator> validators)
{
_validators = validators;
}
public bool Validate(Order order)
{
// И каждого, сука, прогоняем
return _validators.All(v => v.Validate(order));
}
}
3. А если по имени? Тут уже сложнее...
Стандартный контейнер, блядь, из коробки не умеет в именованные регистрации. Типа, "дай мне не абы какой INotifier, а конкретно SmsNotifier". Но народ выкручивается через фабрику — и работает, чёрт возьми!
// Регистрируем каждый тип отдельно
builder.Services.AddTransient<EmailNotifier>();
builder.Services.AddTransient<SmsNotifier>();
// А потом добавляем фабрику, которая по строковому ключу выдает нужную хрень
builder.Services.AddTransient<Func<string, INotifier>>(serviceProvider => key =>
{
return key switch
{
"email" => serviceProvider.GetRequiredService<EmailNotifier>(),
"sms" => serviceProvider.GetRequiredService<SmsNotifier>(),
_ => throw new KeyNotFoundException() // На случай, если прилетела хуйня
};
});
// Используем как боги
public class NotificationService
{
private readonly Func<string, INotifier> _notifierFactory;
public void Notify(string channel)
{
var notifier = _notifierFactory(channel); // О, магия! Получили именно то, что просили.
notifier.Send("Hello");
}
}
Если тебе совсем овердохуища таких сценариев и хочется красоты — посмотри в сторону Autofac или Lamar. Там это из коробки есть. Но для большинства случаев и родного контейнера хватает, главное — понимать, как он работает, а не тыкать наугад, как мартышка в клавиатуру.