Недавно открыл для себя новый DI-фреймворк — RefleX, который, как оказалось, уже давно набирает популярность. Он является аналогом известных многим Zenject/Extenject и VContainer и открыто себя им противопоставляет. Стоит ли этот фреймворк внимания, что лучше выбрать, какие есть альтернативы — об этом расскажу далее, опираясь на свой опыт.


Zenject

Github

Золотая классика DI для Unity. Сам Zenject перестал поддерживаться, поэтому сейчас, говоря Zenject, часто имеют в виду его форк — Extenject, который поддерживается сообществом.

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

Несомненно, в лабораторных условиях, по бенчмаркам, Zenject далеко не чемпион. И в силу своего возраста, и в силу своей тяжеловесности. Порой это действительно становится проблемой. Но в реальном использовании в большинстве случаев проблемы приносит сам разработчик, а не инструмент:

  • Объёмные, Context'ы, а то и глобальные, с большим кол-ом зависимостей;

  • Логика в конструкторах, которая вызывается лениво и неконтролируемо;

  • Отсутствие фазы инициализации, отдельной и управляемой;

  • Неконтролируемое инстанциирование через какой-нибудь FromComponentInNewPrefab;

  • Инжет в GameObject'ы и сцены с большим кол-ом компонентов и дочерних объектов.

И множество других нюансов, которые допускают разработчики прежде чем искать замену Zenject в надежде, что новый инструмент автоматически решит их проблемы.

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

На мой взгляд, основное достоинство и основная проблема Zenject — это его монструозность. Он слишком много умеет и даёт слишком много свободы. Он буквально позволяет сделать всё, что придёт в голову. Ну или, как минимум, не мешает этому.

Достаточно взглянуть на его документацию — она гигантская. В частности, из-за этого немногие её осиливают и знают о всех возможностях, которые предоставляет Zenject.

Например, кто знаком с Reflection Baking, который позволяет ускорить Zenject на IL2CPP и сразу сделать многие бенчмарки и доклады в сети неактуальными?

А там ещё и сигналы, и фабрики, и пулы, и декораторы, и чего только нет.

Таким большим "комбайном", несмотря на коварную удобность, очень сложно управлять. Для неопытного разработчика открывается массу соблазнов, которые неминуемо "выстрелят" в самый неожиданный момент и в самом неожиданном месте.

Но в опытных руках это рабочий, распространённый, проверенный, надёжный, удобный, многофункциональный "засахаренный по самые не балуйся" инструмент. К тому же он отлично работает и за пределами Unity, что позволяет использовать его в SharedLogic (запись в блоге).


VContainer

Github

VContainer — более свежая, легковесная и производительная альтернатива Zenject, которая предоставляет необходимый набор возможностей для реализации именно DI. Здесь намного меньше "сахара" и нет ничего лишнего.

Им можно полностью заменить Zenject. Что-то придётся дописать самостоятельно, где-то обновить синтаксис и немного пересмотреть свои подходы, но в контексте DI они взаимозаменяемы.

Документация небольшая, простая и очень понятная. Есть даже раздел, посвящённый сравнению и переходу с Zenject.

Наделать здесь нелепых ошибок по неопытности намного сложнее. Сам фреймворк чуточку позамороченнее, поэтому сделать какую-то "отсебятину" сходу не получится — фреймворк будет препятствовать, но и не блокировать эту возможность полностью.

Также здесь меньше уровень "автоматизации", и из-за этого вынужденно вырастает уровень контроля. Что положительно сказывается на предсказуемости, стабильности и производительности.

С некоторым "подпиливанием" он тоже выносится за пределы Unity и подключается к SharedLogic.

Есть поддержка UniTask, UniRx, MessagePipe, Unity Entities.

Для пущей оптимизации можно подключить Source Generators и асинхронную или даже параллельную сборку Di-контейнеров.

Не так распространён, как Zenject, но уверенно идёт следующим по популярности среди сторонних решений. Удобный, простой, надёжный. И, конечно, более производительный и компактный.


RefleX

GitHub

При всех громких заявлениях о своём превосходстве, точно не является "убийцей зенжектов". Однако это вполне жизнеспособная альтернатива со своими фишками и синтаксисом, который кому-то может понравиться больше.

Помимо уровня оптимизации можно отметить симпатичное окно отладки, похожее на аналогичное в VContainer. Я подобным пользуюсь редко, поэтому наличие такого инструмента — определённый плюс, но не критически важный.

Что мне не понравилось:

  1. Упрощён по возможностям, возможно в угоду производительности: мало Fluent-дизайна, указание типа только через typeof без дженериков, отсутствие возможности регистрации с дополнительными или переопределёнными параметрами.

  2. Нет поддержки Open Generics (пример из VContainer). Не топ-функция по важности, но частенько встречается в DI-решениях и на больших проектах помогает сократить кол-во бойлерплейта.

  3. Неочевидный способ работы Scope. Он просто ищет Installer'ы через GetComponentInChildren в Runtime и даже не отрисовывает этого в инспекторе. Если бы оно просто сериализовалось до запуска, это было бы более наглядно, контролируемо и "дешевле" (не нужно ничего искать).

  4. Быстрый, но не самый быстрый. Есть опережающие конкуренты среди SparseInject, Pure.DI и ManualDI.

Пример Scope и Installer
Пример Scope и Installer

В остальном это выглядит как нечто среднее между Zenject и VContainer. Возможностей меньше, чем у обоих, но и в производительности, если верить опубликованным бенчмаркам, он их обгоняет. Вполне пригодное для использования решение. Стоит ли тащить его на крупный продакшн — ответить затрудняюсь. Но в простых или чуть менее коммерческих проектах пробовать можно.


Сравнительная таблица

Параметр

Zenject

VContainer

RefleX

Возможности

⭐️⭐️⭐️

⭐️⭐️

⭐️

API

⭐️⭐️⭐️

⭐️⭐️

⭐️

Простота

⭐️

⭐️⭐️

⭐️⭐️⭐️

Производительность

⭐️

⭐️⭐️

⭐️⭐️⭐️

Сообщество

⭐️⭐️⭐️

⭐️⭐️

⭐️

Мой топ

⭐️⭐️

⭐️⭐️⭐️

⭐️


Другие альтернативы

Помимо упомянутых ранее SparseInject, Pure.DI и ManualDI найти другие решения, стоящие внимания, непросто. Если вбить в поиск DI на AssetStore или Github преимущественно будут попадаться варианты типа UniDi, Adic или Init(args).

Можно ещё упомянуть молодой Tarject, который очень похож на Zenject.

Некоторые ECS-фреймворки имеют свой DI, например EcsLite. Но часто не весь проект построен по заветам ECS.

Можно свободно использовать решения из NuGet, ориентированные для dotnet. Но:

  • Вручную потребуется сделать обвязки для Unity-контекста.

  • Могут быть в работе ограничения на определённых платформах.

  • Может быть слишком большое кол-во зависимостей, которые сложно подключить к Unity.

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


Что выбрать

Для коммерческой разработки, где цена каждой ошибки высока, лучше брать стабильные, проверенные и наиболее распространённые решения: Zenject и VContainer. Или понятное контролируемое in-house решение.

Написать свой фреймворк под проект — тоже вариант. Это не так сложно делается.

  • Минус: чем больше проект, тем больше этот фреймворк требует доработок. Насколько он в итоге окажется лучше готового решения, проверенного тысячами разработчиков, — вопрос. И это не считая времени, затраченного на реализацию и поддержку.

  • Плюс: если размер билда критически важен и каждый мегабайт на счету, то собственное решение без лишних зависимостей, содержащее только нужный код, подходит как нельзя лучше. Но здесь можно обойтись и без фреймворка или даже без DI вообще.

Для инди и пет-проектов условия проще: стоит брать то, с чем удобнее, понятнее и интереснее работать и что умеет решать поставленные задачи. Если говорить про рассмотренные варианты выше, то все они отлично подойдут и на 99% закроют все потребности по внедрению зависимостей.

Комментарии (6)


  1. SadOcean
    03.09.2025 08:00

    Для небольших проектов без мультиплеера стоит рассмотреть очень простую альтернативу - Unity сам по себе довольно мощный DI контейнер, который сам резолвит зависимости между компонентами, префабами и ScriptableObjects. Это конечно не вся палитра инструментов, но в целом можно использовать SO как глобальные сервисы (Это основное, зачем людям нужны зависимости).


    1. freeExec
      03.09.2025 08:00

      Не совсем понятно, зачем для этого SO?


      1. aks2dio Автор
        03.09.2025 08:00

        Тут, скорее, SO было больше для примера. Как самый простой в использовании кейс. Вообще подойдёт любая штука, в которую Unity умеет сериализовать данные: SO, компоненты, префабы, сцены.

        И, насколько понял, в этом сценарии уже рассматривается больше ServiceLocator, чем DI.


      1. SadOcean
        03.09.2025 08:00

        Как зависимость, которую вы настроите руками в сцен, а unity сама подставит.
        То есть это буквально инъекция в поля, просто с некоторыми ограничениями (SO и префабы все же не прямые аналоги объектов от Di с разным ЖЦ)

        С другой стороны, используя DI мы сталкиваемся с фундаментальными ограничениями - обычно объекты нужно конструировать через DI. Иначе DI просто используется как сервис локатор.
        Но в Unity как минимум для View слоя есть своя система порождения и свое внедрение зависимостей (конструирование уровня из сцен / префабов / коммитов - по сути это live time система внедрения зависимостей)
        Соответственно они немного конфликтуют и для конструирования объектов нужны какие нибудь костыли (спец методы, спец объект на сцене, спец хуки, экстра код), которые по сути сводят на нет удобство DI

        В идеале DI предназначен делать так, чтобы объект не знал, что ему нужен DI - ему просто давали его зависимости. Чем больше обвеса (атрибуты, внедрения через поля, хаки для старта в unity объектах) - тем меньше толку.

        Если в итоге DI требует boiler plate чтобы работать на уровне объекта (то есть знает про DI) - проще просто засунуть service locator в SO и доставать зависимость из него - код получится проще.


    1. aks2dio Автор
      03.09.2025 08:00

      Согласен. Нет смысла тащить инструмент туда, где он не нужен, и пытаться забивать гвозди микроскопом.

      Про самостоятельный резолв, возможно, слишком лихо написано — всё же Unity не из общей кучи зависимости собирает, а подставляет конкретно заданные зависимости в конкретно заданные места.

      С глобальными сервисами тоже немного перебор. Всё же тут важен контекст применения: на определённых масштабах глобальные сервисы становятся проблемой, а не благом.

      Но, в целом, да Unity, действительно, предоставляет довольно гибкие возможности по внедрению и замене зависимостей "из коробки".


      1. SadOcean
        03.09.2025 08:00

        Да, прямые зависимости на SO конечно кривенько и аналогия не совсем прямая.
        С другой стороны любая другая система зависимостей аналогично требует костылей, производительности и хаков поверх.
        При этом все равно сущности view уровня будут собираться через обычные SerializedField, поэтому использовать вторую систему и синхронизировать их вместе - тоже такое.

        Вообще глобальное ограничение Di - это то, что ты неявно собираешь всю иерархию не через new, но через DI, иначе это по факту ServiceLocator
        Но в Unity уже и так есть свои костыли для порождения игровых объектов и компонентов и хорошие средства для работы с ними.