Каким образом DI контейнер снабжает классы сервисами?

Такой вопрос. Может кто нибудь объяснить как работает DI контейнер в .net? Допустим, я хочу реализовать ioc для nunit тестов: как должен выглядеть DI контейнер? И как правильно создавать экземпляры нужных мне классов, чтобы в их конструкторы автоматически передавались нужные сервисы? В .net core framework объявление экземпляров классов (контроллеров, например) скрыто где-то в дебрях промежуточного ПО, по этому я не совсем понимаю как все это работает. Как именно сервисы попадают в конструкторы? И как можно это повторить в другом проекте, чтобы можно было автоматизировать передачу сервисов в конструкторы, как в фреймворке? Буду благодарен за любую инфу.


Ответы (1 шт):

Автор решения: aepot

Если вопрос о принципе работы, то к примеру у вас есть 2 класса

public class Settings
{
    public string Text { get; set; }
}

public class Worker
{
    private readonly Settings _settings;

    public Worker(Settings settings)
    {
        _settings = settings;
    }
}

Если без контейнера, то запустить всю эту систему весьма просто

var settigns = new Settings();
var worker = new Worker(settings);

Но допустим у вас есть абстрактный контейнер, я возьму синтаксис только что придуманного мной. У него есть билдер ContainerBuilder с обобщенным методом Register, и есть он сам Container с обобщенным методом Resolve.

Тогда выше показанный код будет выглядеть так.

var builder = new ContainerBuilder();
builder.Register<Settings>();
builder.Register<Worker>();

var container = builder.Build();
var worker = container.Resolve<Worker>();

Здесь видно, что вся работа с контейнером разделяется на 2 этапа: регистрация типов и получение экземпляров.

Теперь сама суть контейнера, его смысл. Вам не нужно на этапе создания экземпляров в коде явно указывать, какой экземпляр нужно передать куда в конструктор. То есть при работе с контейнером нет зависимости от изменений кода в классах. Всё что нужно - это зарегистрировать типы, и оно само заработает.

Внутри это работает так: контейнер через рефлексию ищет доступный конструктор и смотрит типы его аргументов. Далее он смотрит у себя, есть ли среди зарегистрированных типов нужные? Если да, то он создает экземпляр нужного типа и далее просто вызывает конструктор используя только созданный экземпляр для передачи в аргумент. Это называется Dependency Injection (DI), а именно Constructor Injection. Если зарегистрированного типа не найдено, контейнер просто бросит исключение и ничего не создаст.

Конечно всё намного сложнее в популярных контейнерах устроено, но смысл инверсии управления тот же. Управление созданием экземпляров автоматизировано внутри контейнера, вы сами в коде ничего не создаете.

Когда у вас 10 типов в приложении - можно легко обойтись без контейнера. А когда 1000, то можно легко запутаться, или изменения в одном классе могут повлечь требования доработок в 50 других классов. Контейнер дает больше свободы при внесении изменений в код.

Когда-то я сам разбирался с этим вопросом здесь: IoC в MVVM для чайников

→ Ссылка