Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
.NET поддерживает проектирование программного обеспечения с возможностью внедрения зависимостей. При таком подходе достигается инверсия управления между классами и их зависимостями. Внедрение зависимостей в .NET — это встроенная часть платформы, а также конфигурация, ведение журнала и шаблон параметров.
Зависимость — это любой объект, от которого зависит другой объект. Рассмотрим следующий класс MessageWriter с методом Write, от которого зависят другие классы:
public class MessageWriter
{
public void Write(string message)
{
Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
}
}
Класс может создать экземпляр MessageWriter класса для использования его Write метода. В следующем примере класс MessageWriter выступает зависимостью класса Worker:
public class Worker : BackgroundService
{
private readonly MessageWriter _messageWriter = new();
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
await Task.Delay(1_000, stoppingToken);
}
}
}
Этот класс создает MessageWriter и напрямую зависит от этого класса. Включенные в код зависимости, как в предыдущем примере, представляют собой определенную проблему. Их следует избегать по следующим причинам:
- Чтобы заменить
MessageWriterдругой реализацией, необходимо изменитьWorkerкласс. - Если у
MessageWriterесть зависимости, классWorkerтакже должен их настроить. В больших проектах, когда отMessageWriterзависят многие классы, код конфигурации растягивается по всему приложению. - Такая реализация плохо подходит для модульных тестов. В приложении нужно использовать имитацию или заглушку в виде класса
MessageWriter, что при таком подходе невозможно.
Внедрение зависимостей решает следующие проблемы с помощью:
- Используется интерфейс или базовый класс для абстрагирования реализации зависимостей.
- Зависимость регистрируется в контейнере служб. .NET предоставляет встроенный контейнер служб IServiceProvider. Службы обычно регистрируются при запуске и добавляются в IServiceCollectionприложение. После добавления всех служб используйте BuildServiceProvider для создания контейнера службы.
- Служба внедряется в конструктор класса там, где он используется. Платформа берет на себя создание экземпляра зависимости и его удаление, когда он больше не нужен.
В качестве примера рассмотрим интерфейс IMessageWriter, который определяет метод Write:
namespace DependencyInjection.Example;
public interface IMessageWriter
{
void Write(string message);
}
Этот интерфейс реализуется конкретным типом, MessageWriter.
namespace DependencyInjection.Example;
public class MessageWriter : IMessageWriter
{
public void Write(string message)
{
Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
}
}
Этот пример кода регистрирует службу IMessageWriter с конкретным типом MessageWriter. Метод AddSingleton регистрирует службу с одним временем существования, временем существования приложения. Подробнее о времени существования служб мы поговорим далее в этой статье.
using DependencyInjection.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();
using IHost host = builder.Build();
host.Run();
В приведенном выше коде пример приложения:
Создает экземпляр построителя ведущих приложений.
Настраивает службы путем регистрации:
- Размещенная
Workerслужба. Дополнительные сведения см. в разделе "Рабочие службы" в .NET. - Интерфейс
IMessageWriterкак одноэлементная служба с соответствующей реализациейMessageWriterкласса.
- Размещенная
Создает узел и запускает его.
Узел содержит поставщика услуг внедрения зависимостей. Он также содержит все другие соответствующие службы, необходимые для автоматического создания экземпляра Worker и предоставления соответствующей IMessageWriter реализации в качестве аргумента.
namespace DependencyInjection.Example;
public sealed class Worker(IMessageWriter messageWriter) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
await Task.Delay(1_000, stoppingToken);
}
}
}
При использовании шаблона внедрения зависимостей рабочая служба имеет следующие характеристики:
- Не использует конкретный тип
MessageWriter, толькоIMessageWriterинтерфейс, который он реализует. Это упрощает изменение реализации, которую использует рабочая служба, не изменяя рабочую службу. - Не создает экземпляр
MessageWriter. Контейнер DI создает экземпляр.
Реализацию интерфейса можно улучшить с помощью встроенного IMessageWriter API ведения журнала:
namespace DependencyInjection.Example;
public class LoggingMessageWriter(
ILogger<LoggingMessageWriter> logger) : IMessageWriter
{
public void Write(string message) =>
logger.LogInformation("Info: {Msg}", message);
}
Обновленный метод AddSingleton регистрирует новую реализацию IMessageWriter:
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
Тип HostApplicationBuilder (builder) является частью Microsoft.Extensions.Hosting пакета NuGet.
LoggingMessageWriter зависит от ILogger<TCategoryName>, который запрашивается в конструкторе.
ILogger<TCategoryName> — это предоставленная платформой служба.
Использование цепочки внедрений зависимостей не является чем-то необычным. Каждая запрашиваемая зависимость запрашивает собственные зависимости. Контейнер разрешает зависимости в графе и возвращает полностью разрешенную службу. Коллективный набор зависимостей, которые необходимо разрешить, обычно называется деревом зависимостей, графом зависимостей или графом объектов.
Контейнер разрешает ILogger<TCategoryName>, используя преимущества (универсальных) открытых типов, что устраняет необходимость регистрации каждого (универсального) сконструированного типа.
В терминологии внедрения зависимостей — служба:
- Обычно является объектом, предоставляющим службу для других объектов, например службу
IMessageWriter. - Не связан с веб-службой, хотя служба может использовать веб-службу.
Платформа предоставляет эффективную систему ведения журнала. Реализации IMessageWriter, показанные в предыдущих примерах, демонстрируют базовый DI, а не логирование. Большинству приложений не нужно писать средства ведения журнала. Следующий код демонстрирует использование ведения журнала по умолчанию, которое требует Worker регистрации только в качестве размещенной службы AddHostedService:
public sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1_000, stoppingToken);
}
}
}
Используя предыдущий код, нет необходимости обновлять Program.cs, так как платформа предоставляет ведение журнала.
Несколько правил обнаружения конструктора
Если тип определяет более одного конструктора, поставщик служб включает логику для определения используемого конструктора. Выбирается тот конструктор, который имеет больше всего параметров, в которых типы могут разрешаться с внедрением зависимостей. Рассмотрим следующий пример службы на C#:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(ILogger<ExampleService> logger)
{
// omitted for brevity
}
public ExampleService(FooService fooService, BarService barService)
{
// omitted for brevity
}
}
В приведенном выше коде предполагается, что добавлено ведение журнала и оно доступно через поставщика услуг, но типы FooService и BarService недоступны. Конструктор с параметром ILogger<ExampleService> инициализирует экземпляр ExampleService. Несмотря на то что есть конструктор, определяющий больше параметров, типы FooService и BarService не могут быть разрешены с помощью DI.
Если при обнаружении конструкторов возникает неоднозначность, создается исключение. Рассмотрим следующий пример службы на C#:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(ILogger<ExampleService> logger)
{
// omitted for brevity
}
public ExampleService(IOptions<ExampleOptions> options)
{
// omitted for brevity
}
}
Предупреждение
Код ExampleService с неоднозначными параметрами типа, разрешаемыми DI, создает исключение.
Не делайте этого— это предназначено для демонстрации того, что означает "неоднозначные типы, разрешаемые DI".
В предыдущем примере существует три конструктора. Первый конструктор не имеет параметров и не требует служб от поставщика служб. Предположим, что в контейнер внедрения зависимостей были добавлены ведение журнала и параметры, а службы могут разрешаться с внедрением зависимостей. Когда контейнер DI пытается разрешить ExampleService тип, он создает исключение, так как два конструктора являются неоднозначными.
Избегайте неоднозначности путем определения конструктора, который принимает оба разрешаемых типа DI, вместо этого:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(
ILogger<ExampleService> logger,
IOptions<ExampleOptions> options)
{
// omitted for brevity
}
}
Регистрация групп служб с помощью методов расширения
Расширения Microsoft используют конвенцию для регистрации группы связанных служб. Соглашение заключается в использовании одного метода расширения Add{GROUP_NAME} для регистрации всех служб, необходимых компоненту платформы. Например, метод расширения AddOptions регистрирует все службы, необходимые для работы с параметрами.
Платформенные службы
При использовании любого из доступных шаблонов узла или построителя приложений применяются значения по умолчанию, а службы регистрируются платформой. Рассмотрим некоторые из самых популярных шаблонов построителя узлов и приложений:
- Host.CreateDefaultBuilder()
- Host.CreateApplicationBuilder()
- WebHost.CreateDefaultBuilder()
- WebApplication.CreateBuilder()
- WebAssemblyHostBuilder.CreateDefault
- MauiApp.CreateBuilder
После создания построителя из любого из этих API в системе есть службы, определяемые платформой, в зависимости от того, IServiceCollectionкак вы настроили узел. Для приложений на основе шаблонов .NET платформа может зарегистрировать сотни служб.
В следующей таблице перечислены некоторые примеры этих зарегистрированных платформой служб.
| Тип службы | Время существования |
|---|---|
| Microsoft.Extensions.DependencyInjection.IServiceScopeFactory | Отдельная |
| IHostApplicationLifetime | Отдельная |
| Microsoft.Extensions.Logging.ILogger<TCategoryName> | Отдельная |
| Microsoft.Extensions.Logging.ILoggerFactory | Отдельная |
| Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Отдельная |
| Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Временный |
| Microsoft.Extensions.Options.IOptions<TOptions> | Отдельная |
| System.Diagnostics.DiagnosticListener | Отдельная |
| System.Diagnostics.DiagnosticSource | Отдельная |
Время существования служб
Службы можно зарегистрировать с одним из следующих вариантов времени существования:
Они описываются в следующих разделах. Для каждой зарегистрированной службы выбирайте подходящее время существования.
Временный
Временные службы времени существования создаются при каждом их запросе из контейнера служб. Чтобы зарегистрировать службу в качестве временной, вызовите AddTransient.
В приложениях, обрабатывающих запросы, временные службы удаляются в конце запроса. Это время существования повлечет за собой выделение по запросу, так как службы разрешаются и создаются каждый раз. Дополнительные сведения см. в руководстве по внедрению зависимостей: рекомендации по IDisposable для временных и общих экземпляров.
Ограниченные
Для веб-приложений время существования, привязанное к области, означает, что службы создаются один раз для каждого запроса (подключения) клиента. Регистрируйте службы с заданной областью с помощью AddScoped.
В приложениях, обрабатывающих запросы, службы с заданной областью удаляются в конце запроса.
Примечание.
При использовании Entity Framework Core метод расширения AddDbContext по умолчанию регистрирует типы DbContext с заданной областью времени существования.
Служба с заданной областью всегда должна использоваться из области — неявной (например, области ASP.NET Core для каждого запроса) или явной области, созданной с помощью IServiceScopeFactory.CreateScope().
Не извлекайте службу ограниченной области непосредственно из одиночного объекта с помощью внедрения конструктора или запрашивая её в одиночном объекте. Это приводит к тому, что объект службы ведет себя как одиночка, что может вызвать некорректное состояние при обработке последующих запросов.
При создании и использовании явной области с IServiceScopeFactory разрешается разрешать скоуп-службу в рамках синглтона.
Также допустимо:
- Разрешение одноэлементной службы из службы с заданной областью или временной службы.
- Разрешение службы с заданной областью из другой службы с заданной областью или временной службы.
По умолчанию в среде разработки разрешение службы из другой службы с более длинным временем существования вызывает исключение. Дополнительные сведения см. в разделе Проверка области.
Отдельная
Одноэлементные службы времени существования создаются в следующих случаях.
- При первом запросе.
- Разработчиком при предоставлении экземпляра реализации непосредственно в контейнер. Этот подход требуется достаточно редко.
Каждый последующий запрос на реализацию службы из контейнера внедрения зависимостей использует тот же экземпляр. Если в приложении нужно использовать одноэлементные службы, разрешите контейнеру служб управлять временем их существования. Не реализуйте одноэлементный подход и предоставьте код для удаления одноэлементных объектов. Службы никогда не должны удаляться кодом, который разрешил службу из контейнера. Если тип или фабрика зарегистрированы как одноэлементный объект, контейнер автоматически удалит одноэлементные объекты.
Зарегистрируйте одноэлементные службы с помощью AddSingleton. Одноэлементные службы должны быть потокобезопасными и часто использоваться в службах без отслеживания состояния.
В приложениях, обрабатывающих запросы, отдельные службы удаляются, когда ServiceProvider удаляется по завершении работы приложения. Так как память не освобождается до завершения работы приложения, рассмотрите возможность использования памяти с одной службой.
Методы регистрации службы
Платформа предоставляет методы расширения регистрации службы, которые полезны в определенных сценариях.
| Способ | Автоматически объект удаление |
Несколько реализации |
Передача аргументов |
|---|---|---|---|
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()Пример: services.AddSingleton<IMyDep, MyDep>(); |
Да | Да | Нет |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})Примеры: services.AddSingleton<IMyDep>(sp => new MyDep());services.AddSingleton<IMyDep>(sp => new MyDep(99)); |
Да | Да | Да |
Add{LIFETIME}<{IMPLEMENTATION}>()Пример: services.AddSingleton<MyDep>(); |
Да | Нет | Нет |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})Примеры: services.AddSingleton<IMyDep>(new MyDep());services.AddSingleton<IMyDep>(new MyDep(99)); |
Нет | Да | Да |
AddSingleton(new {IMPLEMENTATION})Примеры: services.AddSingleton(new MyDep());services.AddSingleton(new MyDep(99)); |
Нет | Нет | Да |
Дополнительные сведения об удалении типа см. в разделе Удаление служб.
Регистрация службы только с типом реализации эквивалентна регистрации этой службы с той же реализацией и типом службы. Рассмотрим следующий пример кода:
services.AddSingleton<ExampleService>();
Это эквивалентно регистрации службы как в службе, так и в реализации одного и того же типа:
services.AddSingleton<ExampleService, ExampleService>();
Эта эквивалентность заключается в том, что несколько реализаций службы не могут быть зарегистрированы с помощью методов, которые не принимают явный тип службы. Эти методы могут регистрировать несколько экземпляров службы, но все они имеют одинаковый тип реализации.
Любой из методов регистрации службы можно использовать для регистрации нескольких экземпляров службы одного типа службы. В следующем примере метод AddSingleton вызывается дважды с типом службы IMessageWriter. Второй вызов AddSingleton переопределяет предыдущий, если он разрешается как IMessageWriter, и добавляет к предыдущему, если несколько служб разрешаются через IEnumerable<IMessageWriter>. Службы отображаются в том порядке, в котором они были зарегистрированы при разрешении через IEnumerable<{SERVICE}>.
using ConsoleDI.IEnumerableExample;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
builder.Services.AddSingleton<ExampleService>();
using IHost host = builder.Build();
_ = host.Services.GetService<ExampleService>();
await host.RunAsync();
Предыдущий пример исходного кода регистрирует две реализации IMessageWriter.
using System.Diagnostics;
namespace ConsoleDI.IEnumerableExample;
public sealed class ExampleService
{
public ExampleService(
IMessageWriter messageWriter,
IEnumerable<IMessageWriter> messageWriters)
{
Trace.Assert(messageWriter is LoggingMessageWriter);
var dependencyArray = messageWriters.ToArray();
Trace.Assert(dependencyArray[0] is ConsoleMessageWriter);
Trace.Assert(dependencyArray[1] is LoggingMessageWriter);
}
}
Компонент ExampleService определяет два параметра конструктора: одиночный IMessageWriter и IEnumerable<IMessageWriter>. Единственный IMessageWriter является последней реализацией, которая зарегистрирована, в то время как IEnumerable<IMessageWriter> представляет все зарегистрированные реализации.
Платформа также предоставляет методы расширения TryAdd{LIFETIME}, которые регистрируют службу только в том случае, если реализация еще не зарегистрирована.
В следующем примере вызов AddSingleton регистрирует ConsoleMessageWriter как реализацию для IMessageWriter. Вызов TryAddSingleton ничего не делает, поскольку у IMessageWriter уже есть зарегистрированная реализация:
services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();
TryAddSingleton не оказывает эффекта, так как оно уже было добавлено, и "try" завершается неудачей.
ExampleService утверждает следующее:
public class ExampleService
{
public ExampleService(
IMessageWriter messageWriter,
IEnumerable<IMessageWriter> messageWriters)
{
Trace.Assert(messageWriter is ConsoleMessageWriter);
Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);
}
}
Дополнительные сведения см. в разделе:
Методы TryAddEnumerable(ServiceDescriptor) регистрируют службу только в том случае, если еще не существует реализации того же типа. Несколько служб разрешается через IEnumerable<{SERVICE}>. При регистрации служб добавьте экземпляр, если один из тех же типов еще не добавлен. Авторы библиотек используют TryAddEnumerable, чтобы избежать регистрации нескольких копий реализации в контейнере.
В следующем примере первый вызов TryAddEnumerable регистрирует MessageWriter как реализацию для IMessageWriter1. Второй вызов регистрирует MessageWriter для IMessageWriter2. Третий вызов ничего не делает, поскольку у IMessageWriter1 уже есть зарегистрированная реализация MessageWriter:
public interface IMessageWriter1 { }
public interface IMessageWriter2 { }
public class MessageWriter : IMessageWriter1, IMessageWriter2
{
}
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter2, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
Регистрация сервиса не зависит от порядка, за исключением регистрации нескольких реализаций одного типа.
IServiceCollection является коллекцией объектов ServiceDescriptor. В следующем примере показано, как зарегистрировать службу, создав и добавив ServiceDescriptor:
string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
typeof(IMessageWriter),
_ => new DefaultMessageWriter(secretKey),
ServiceLifetime.Transient);
services.Add(descriptor);
Встроенные методы Add{LIFETIME} используют аналогичный подход. Например, см. исходный код для AddScoped.
Поведение внедрения через конструктор
Службы можно настроить с помощью:
- IServiceProvider
-
ActivatorUtilities:
- Создает объекты, которые не зарегистрированы в контейнере.
- Используется в сочетании с некоторыми возможностями платформы.
Конструкторы могут принимать аргументы, которые не предоставляются внедрением зависимостей, но эти аргументы должны назначать значения по умолчанию.
При IServiceProvider разрешении ActivatorUtilities служб внедрение конструктора требует общедоступного конструктора.
При ActivatorUtilities разрешении служб внедрение конструктора требует наличия только одного применимого конструктора. Перегрузки конструктора поддерживаются, но может существовать всего одна перегрузка, все аргументы которой могут быть обработаны с помощью внедрения зависимостей.
Проверка области
Когда приложение выполняется в Development среде и вызывает CreateApplicationBuilder для сборки узла, поставщик служб по умолчанию выполняет проверки, чтобы убедиться, что:
- Службы с заданной областью не разрешаются из корневого поставщика службы.
- Службы с заданной областью не вводятся в одноэлементные объекты.
Корневой поставщик службы создается при вызове BuildServiceProvider. Время существования корневого поставщика службы соответствует времени существования приложения — поставщик запускается с приложением и удаляется, когда приложение завершает работу.
Службы с заданной областью удаляются создавшим их контейнером. Если служба с заданной областью создается в корневом контейнере, время существования службы повышается до уровня одноэлементного объекта, поскольку она удаляется только корневым контейнером при завершении работы приложения. Проверка областей службы перехватывает эти ситуации при вызове BuildServiceProvider.
Сценарии применения области
Интерфейс IServiceScopeFactory всегда регистрируется как отдельный (singleton), но IServiceProvider зависит от времени существования содержащего класса. Например, если вы разрешаете службы из области, и одна из этих служб использует обрабатываемый IServiceProvider, это экземпляр с областью действия.
Чтобы определить область охвата служб в реализации IHostedService, такие как BackgroundService, не внедряйте зависимости служб с помощью внедрения конструктора. Вместо этого внедрите IServiceScopeFactory, создайте область, а затем используйте разрешение зависимостей из области, чтобы применить подходящее время существования служб.
namespace WorkerScope.Example;
public sealed class Worker(
ILogger<Worker> logger,
IServiceScopeFactory serviceScopeFactory)
: BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
try
{
logger.LogInformation(
"Starting scoped work, provider hash: {hash}.",
scope.ServiceProvider.GetHashCode());
var store = scope.ServiceProvider.GetRequiredService<IObjectStore>();
var next = await store.GetNextAsync();
logger.LogInformation("{next}", next);
var processor = scope.ServiceProvider.GetRequiredService<IObjectProcessor>();
await processor.ProcessAsync(next);
logger.LogInformation("Processing {name}.", next.Name);
var relay = scope.ServiceProvider.GetRequiredService<IObjectRelay>();
await relay.RelayAsync(next);
logger.LogInformation("Processed results have been relayed.");
var marked = await store.MarkAsync(next);
logger.LogInformation("Marked as processed: {next}", marked);
}
finally
{
logger.LogInformation(
"Finished scoped work, provider hash: {hash}.{nl}",
scope.ServiceProvider.GetHashCode(), Environment.NewLine);
}
}
}
}
}
В приведенном выше коде во время выполнения приложения фоновая служба:
- зависит от IServiceScopeFactory;
- Создает объект IServiceScope для разрешения других служб.
- разрешает службы с заданной областью для использования;
- обрабатывает объекты, затем ретранслирует их и в итоге помечает как обработанные.
В примере исходного кода можно увидеть, как реализации IHostedService могут использовать преимущества времени существования служб с заданной областью.
Ключи служб
Начиная с .NET 8, существует поддержка регистрации служб и их поиска на основе ключа, то есть можно зарегистрировать несколько служб с разными ключами и использовать эти ключи для поиска.
Например, рассмотрим ситуацию, когда у вас есть различные реализации интерфейса IMessageWriter: MemoryMessageWriter и QueueMessageWriter.
Эти службы можно зарегистрировать с помощью перегрузки методов регистрации службы (см. ранее), поддерживающих ключ в качестве параметра:
services.AddKeyedSingleton<IMessageWriter, MemoryMessageWriter>("memory");
services.AddKeyedSingleton<IMessageWriter, QueueMessageWriter>("queue");
Функциональность key не ограничена string. Он key может быть любым object, который вы хотите, при условии, что тип правильно реализует Equals.
В конструкторе класса, который используется IMessageWriter, добавьте FromKeyedServicesAttribute ключ службы для разрешения:
public class ExampleService
{
public ExampleService(
[FromKeyedServices("queue")] IMessageWriter writer)
{
// Omitted for brevity...
}
}
См. также
- Основные сведения о внедрении зависимостей в .NET
- Использование внедрения зависимостей в .NET
- Рекомендации по внедрению зависимостей
- Использование внедрения зависимостей в ASP.NET Core
- Шаблоны конференций NDC для разработки приложений с внедрением зависимостей
- Принцип явных зависимостей
- Контейнеры с инверсией управления и шаблон внедрения зависимостей (Мартин Фаулер (Martin Fowler))
- Запросы на исправление ошибок, связанных с внедрением зависимостей, следует создавать в репозитории github.com/dotnet/extensions