Недавно в этом посте вы познакомились с библиотекой Pure.DI. Этот пакет с анализатором/генератором кода .NET 5 задумывался как помощник, который пишет простой код для композиции объектов в стиле чистого DI, используя подсказки для построения графа зависимостей. Он следит за изменениями, анализирует типы и зависимости между ними, подсвечивает проблемы и предлагает пути решения. Важно отметить, что библиотека Pure.DI - это не контейнер внедрения зависимостей, в её задачи входит:

  • анализ графа зависимостей

  • определение в нем проблем и путей их решения

  • создание эффективного кода для композиции объектов

По обсуждениям в предыдущем посте у меня сложилось впечатление, что необходимо решить следующие вопросы:

  • добавить возможность использовать Pure.DI в инфраструктуре ASP.NET

  • убрать бинарную зависимость на API из пакета Pure.DI.Contracts

  • увеличить производительность для случаев, когда операция Resolve() выполняется многократно

Сейчас, после небольших доработок анализатор кода автоматически определяет, является ли проект ASP.NET проектом, и генерирует код специального метода расширения, который обеспечивает интеграцию с ASP.NET. Для демонстрации возможности выбрано серверное Blazor приложение:

Описание графа зависимостей находится в этом классе:

DI.Setup()
  .Bind<IDispatcher>().As(Singleton).To<Dispatcher>()
  .Bind<IClockViewModel>().To<ClockViewModel>()
  .Bind<ITimer>().As(Scoped).To(_ => new Timer(TimeSpan.FromSeconds(1)))
  .Bind<IClock>().As(ContainerSingleton).To<SystemClock>();

Для того чтобы связать эти DI типы с инфраструктурой  ASP.NET нужно добавить всего лишь одну строку вызова метода расширения:

services.AddClockDomain();

Как видно из примера, его название зависит от имени класса-владельца, чтобы не путаться если их несколько. Помимо метода расширения, добавлены два времени жизни:

  • ContainerSingleton - чтобы использовать один объект типа на ASP.NET контейнер

  • Scoped - чтобы использовать по одному объекту типа на каждый ASP.NET scope

Сейчас их нельзя использовать вне контекста ASP.NET, иначе появится ошибка компиляции с информацией об этом.

Для решения вопроса о нежелательной бинарной зависимости на API я “удалил” пакет Pure.DI.Contracts. Теперь весь API для описания графа зависимостей генерируются “на месте” и является частью инфраструктурного кода проекта, где этот API и используется. Как итог, в проекты не добавляется ни одной бинарной зависимости, а единственная зависимость типа analyzers на пакет Pure.DI будет использована только во время компиляции и забыта сразу после. И, конечно, ее можно использовать без ограничения в любых проектах, не опасаясь “зависеть от чего-то лишнего”.

ASP.NET инфраструктура вызывает метод Resolve() для каждого запроса. Чтобы уменьшить накладные расходы на этот вызов, был оптимизирован код, ответственный за сопоставление типа корневого элемента композиции объектов к методу создания этой композиции. С результатами сравнительных тестов можно ознакомиться здесь. Хотелось бы подчеркнуть, что в этом сравнении используется спорный способ получения показателей производительности. Поэтому, эти результаты, дают приблизительную оценку накладных расходов на многократный вызов метода Resolve().

Как обычно, любые конструктивные замечания и идеи очень приветствуются.