Основные различия
1. Способ регистрации
Статические обработчики задаются на этапе компиляции: класс помечается атрибутом [Handles("ключ")] и реализует IMessageHandler<T>. Они автоматически находятся при сканировании сборок и регистрируются в DI как singleton.
Динамические подписки создаются в рантайме через IDynamicSubscriptionManager.Subscribe<T>(key, handler). Можно подписаться лямбдой, методом, делегатом — не требуется отдельный класс или атрибут.
2. Время жизни
Статические существуют всё время работы приложения (пока жив DI-контейнер). Их нельзя убрать выборочно — только если полностью остановить хаб.
Динамические могут быть временными: подписка живёт, пока не будет вызван Dispose() возвращаемого токена. Можно подписаться на несколько секунд (например, ожидая ответ) и отписаться.
3. Порядок вызова
В режиме Both сначала всегда вызываются все статические обработчики (в порядке, определённом сканированием), а затем — все динамические (в порядке регистрации). Это происходит синхронно внутри одного цикла обработки сообщения.
4. Гибкость и предсказуемость
Статические дают жёсткую структуру: вы точно знаете, какие события кем обрабатываются, всё проверяется при старте. Подходит для обязательной бизнес-логики, аудита, инфраструктурных событий.
Динамические дают гибкость: можно подписываться и отписываться в зависимости от состояния приложения, добавлять обработчики без перекомпиляции (плагины, скрипты). Но есть риск случайно отписаться или пропустить событие.
5. Производительность
Статические чуть быстрее: реестр EventHandlerRegistry отдаёт готовый список типов, экземпляры обработчиков уже созданы в DI.
Динамические требуют поиска по ConcurrentDictionary и, возможно, упаковки делегатов, но накладные расходы минимальны.
6. Тестирование
Статические требуют настройки DI и сканирования сборок, но дают полную уверенность в работе всех штатных обработчиков.
Динамические позволяют в тесте быстро подписать лямбду, собрать данные и проверить их — меньше инфраструктуры.
📊 Сравнительная таблица
🤝 Можно ли их совмещать?
Да, в режиме RoutingMode.Both оба типа работают одновременно. Сообщения обрабатываются в одной очереди строго по порядку: сначала отрабатывают статические обработчики, потом динамические. Это позволяет отделить постоянную инфраструктуру от гибкой функциональности без конфликтов.
Таким образом, выбор зависит от того, нужна ли вам неизменная и проверяемая структура (статика) или лёгкость добавления/удаления обработчиков в процессе работы (динамика).
Динамические подписки создаются в рантайме через IDynamicSubscriptionManager.Subscribe<T>(key, handler). Можно подписаться лямбдой, методом, делегатом — не требуется отдельный класс или атрибут.
-------
В вашей шине статические обработчики и динамические подписки — это два разных механизма реагирования на события, которые отличаются способом регистрации, временем жизни, производительностью и сценариями использования.
🔍 Основные различия
1. Способ регистрации
Статические обработчики задаются на этапе компиляции: класс помечается атрибутом [Handles("ключ")] и реализует IMessageHandler<T>. Они автоматически находятся при сканировании сборок и регистрируются в DI как singleton.
Динамические подписки создаются в рантайме через IDynamicSubscriptionManager.Subscribe<T>(key, handler). Можно подписаться лямбдой, методом, делегатом — не требуется отдельный класс или атрибут.
2. Время жизни
Статические существуют всё время работы приложения (пока жив DI-контейнер). Их нельзя убрать выборочно — только если полностью остановить хаб.
Динамические могут быть временными: подписка живёт, пока не будет вызван Dispose() возвращаемого токена. Можно подписаться на несколько секунд (например, ожидая ответ) и отписаться.
3. Порядок вызова
В режиме Both сначала всегда вызываются все статические обработчики (в порядке, определённом сканированием), а затем — все динамические (в порядке регистрации). Это происходит синхронно внутри одного цикла обработки сообщения.
4. Гибкость и предсказуемость
Статические дают жёсткую структуру: вы точно знаете, какие события кем обрабатываются, всё проверяется при старте. Подходит для обязательной бизнес-логики, аудита, инфраструктурных событий.
Динамические дают гибкость: можно подписываться и отписываться в зависимости от состояния приложения, добавлять обработчики без перекомпиляции (плагины, скрипты). Но есть риск случайно отписаться или пропустить событие.
5. Производительность
Статические чуть быстрее: реестр EventHandlerRegistry отдаёт готовый список типов, экземпляры обработчиков уже созданы в DI.
Динамические требуют поиска по ConcurrentDictionary и, возможно, упаковки делегатов, но накладные расходы минимальны.
6. Тестирование
Статические требуют настройки DI и сканирования сборок, но дают полную уверенность в работе всех штатных обработчиков.
Динамические позволяют в тесте быстро подписать лямбду, собрать данные и проверить их — меньше инфраструктуры.
📊 Сравнительная таблица
Характеристика Статические обработчики Динамические подписки
Когда определяются На этапе компиляции (атрибуты) В рантайме (вызов метода)
Время жизни Постоянное (пока жив DI) Может быть временным (через Dispose)
Регистрация Автоматическая (сканирование сборок) Явная (Subscribe<T>())
Тип обработчика Класс, реализующий IMessageHandler<T> Делегат (лямбда/метод)
Гибкость Низкая (нельзя убрать один обработчик) Высокая (можно подписать/отписать в любой момент)
Предсказуемость Высокая (всё видно при старте) Ниже (легко забыть или случайно отписать)
Производительность Немного выше (готовый реестр) Незначительно ниже (словарь + делегат)
Использование Бизнес-логика, аудит, обязательные события UI, плагины, временные реакции, тесты
🤝 Можно ли их совмещать?
Да, в режиме RoutingMode.Both оба типа работают одновременно. Сообщения обрабатываются в одной очереди строго по порядку: сначала отрабатывают статические обработчики, потом динамические. Это позволяет отделить постоянную инфраструктуру от гибкой функциональности без конфликтов.
Таким образом, выбор зависит от того, нужна ли вам неизменная и проверяемая структура (статика) или лёгкость добавления/удаления обработчиков в процессе работы (динамика).