В 2010 году в C# 4.0 появилось ключевое слово dynamic. Тогда казалось, что это очень круто — наконец‑то можно обойти строгую типизацию там, где она откровенно мешает. Прошло пятнадцать лет, и эйфория давно улеглась. Теперь dynamic воспринимается скорее как специализированный инструмент с узкой областью применения.
Суть проблемы
Dynamic переносит проверку типов с compile‑time на runtime. Компилятор отступает и даёт вам полную свободу — проверяйте типы сами. Это позволяет писать код, который выглядит как на динамических языках вроде Python или JavaScript, оставаясь при этом внутри C#.
Классический пример — работа с COM‑объектами. До появления dynamic вызов методов Excel через COM Interop выглядел как издевательство, нескончаемые касты к интерфейсам, InvokeMember, пляски с object[] для параметров. Сравните старый и новый подход:
// было
Type excelType = Type.GetTypeFromProgID("Excel.Application");
object excel = Activator.CreateInstance(excelType);
excelType.InvokeMember("Visible", BindingFlags.SetProperty,
null, excel, new object[] { true });
// ...и так далее на несколько страниц
// стало, адекватный код
Type excelType = Type.GetTypeFromProgID("Excel.Application");
dynamic excel = Activator.CreateInstance(excelType);
excel.Visible = true;
excel.Workbooks.Add();
dynamic sheet = excel.ActiveSheet;
sheet.Cells[1, "A"].Value = "Привет из C#";
Разница колоссальная. Код стал читаемым, похожим на VBA‑скрипты для Excel, которые все знают. Для таких сценариев dynamic и создавался.
Где dynamic оправдан
COM и legacy interop. Автоматизация Office, работа со старыми ActiveX‑компонентами, вызов методов из COM‑библиотек. Здесь dynamic экономит огромное количество времени и избавляет от шаблонного кода. Альтернатива в том, чтобы подключать тяжеловесные Primary Interop Assembly или писать обёртки через рефлексию. Оба варианта хуже.
Разбор непредсказуемых данных. Иногда приходит JSON от внешнего API, из которого нужны два‑три поля. Структура может меняться или содержать сотни ненужных вложений. Описывать классы под всё это сомнительное удовольствие:
string json = GetWebhookPayload();
dynamic data = JsonConvert.DeserializeObject(json);
string orderId = data.payload.order.id;
Это спорный подход, но он работает для одноразовых задач или утилит. Лучше все таки определить модель хотя бы для критичных полей.
Обёртка над DLR‑языками. Если интегрируете IronPython или другие динамические языки через Dynamic Language Runtime, dynamic делает взаимодействие естественным. Альтернатива копаться в API движка, что не очень.
Альтернатива примитивной рефлексии. Бывает нужно вызвать метод, имя которого известно только в runtime. Можно написать Type.GetMethod(...).Invoke(...) со всеми вытекающими проблемами (boxing значимых типов, медленный поиск методов, громоздкий код). А можно ((dynamic)obj).MethodName(), проще и часто быстрее за счёт кеширования в DLR.
Техническая изнанка
Каждый вызов через dynamic компилятор превращает в конструкцию вида:
CallSite<Func<CallSite, object, object, object>> site =
CallSite<Func<CallSite, object, object, object>>.Create(
Binder.InvokeMember(...));
site.Target(site, obj, arg);
CallSite — это механизм кеширования. При первом вызове DLR через рефлексию ищет метод у конкретного типа и создаёт делегат. При повторных вызовах с тем же типом используется закешированный делегат — это быстрее чистой рефлексии. Но всё равно медленнее статического вызова.
Кеш работает на уровне конкретной точки вызова. Если в разных местах кода вызываете один метод через dynamic, каждое место получает свой кеш. Если тип объекта меняется, кеш не помогает и приходится искать заново.
Цена вопроса
Производительность — главная проблема. Сложение чисел в цикле показывает разницу в ~18 раз между статическим int и dynamic. Причины:
поиск метода через DLR (даже с кешированием)
boxing/unboxing для значимых типов
доп. аллокации памяти
overhead от CallSite‑инфраструктуры
В горячих циклах, алгоритмах обработки данных, численных расчётах dynamic абсолютно неприемлем. Даже в бизнес‑логике, которая выполняется тысячи раз в секунду, стоит дважды подумать.
Но производительность не единственная проблема. Страдает и надёжность:
Ошибки в runtime вместо compile‑time. Если оечатались в имени свойства, то получите RuntimeBinderException, если тесты не покрыли этот путь.
Проблемы с рефакторингом. Rename в IDE пропустит все места с dynamic.
Распространение по коду. Стоит одному методу вернуть dynamic, и все потребители либо тоже становятся dynamic, либо занимаются ручным кастом. Читал про проект, где вся модель данных была на dynamic без классов вообще.
Где dynamic точно не нужен
Бизнес‑логика и модели данных. Если структура данных известна, опишите её явно. Потратите пять минут на создание класса, зато получите типобезопасность, документацию, удобство отладки.
Производительно‑критичные участки. Алгоритмы, обработка больших объёмов данных, графика, реалтайм. Каждый динамический вызов — это микропауза, которая в сумме даёт значимую просадку.
Публичные API библиотек. Не заставляйте пользователей работать с dynamic без крайней необходимости.
Крупные командные проекты. Без статической типизации сложно гарантировать правильное использование API между разработчиками. Ошибки всплывут позже и обойдутся дороже.
Что с этим делать
Всегда спрашивайте себя: можно ли обойтись дженериками, интерфейсами, паттернами?
Dynamic— инструмент последней надежды.Изолируйте динамический код. Заворачивайте работу с
dynamicв отдельные методы или классы. Возвращайте строгие типы или хотя быDictionary<string, object>, чтобы не размазыватьdynamicпо всему коду.Обрабатывайте RuntimeBinderException. Если вызываете метод, который может отсутствовать, предусмотрите try‑catch. Или проверяйте существование через рефлексию заранее.
Профилируйте. Увидели
dynamicв методе, который вызывается часто? Проведите бенчмарк. Убедитесь, что просадка производительности приемлема, или переработайте безdynamic.Документируйте использование. Код с
dynamicтребует комментариев — объясните, почему здесь нельзя было использовать статические типы. Будущие разработчики скажут спасибо.
Dynamic упрощает интеграцию с чуждым миром COM‑объектов и динамических языков. Но все это требует осторожности.

Если хотите глубже разобраться, где в C# проходит граница между удобством и ответственным дизайном, полезно пройти путь от баз языка до архитектурных решений на практике. На специализации C# Developer это разбирают последовательно: от ООП и .NET до асинхронности, работы с памятью и командной разработки — с фокусом на осознанный, типобезопасный код.
Чтобы узнать больше о формате обучения и познакомиться с преподавателями, приходите на бесплатные демо-уроки:
25 декабря: Основы работы с Telegram API. Записаться
14 января: Linq на практике. Записаться
22 января: Сетевой чат на C#. Записаться
Комментарии (5)

virst
23.12.2025 17:51Занимательная статья. Но я добавлю свои 5 копеек, моего опыта. dynamic иногда так весело стреляет в рантайме на проде, что это приучило меня: лучше вынести громоздкие конструкции в свой медот и дергать его или написать руками через рефлексию , подготовить свои обертки и прочие приемы , но не надеется на dynamic. И возможно вы меня осудите , но джунов за dynamic я бью линейкой по рукам.

cstrike
23.12.2025 17:51Если вы используете dynamic, значит вы или делаете что-то очень умное или что-то очень неправильное.

pashagoroshko
23.12.2025 17:51dynamic и JSON не понял, какую проблему решает ваш пример. Как правило метод не меняет просто так структуру JSON, а для других случаев всякие jsonObject(в newtonsoft и systemTextJson).
Лучше не использовать никогда, всегда есть адекватная альтернатива.
navferty
Нелогично. Если имя метода будет известно только в рантайме, то dynamic особо не поможет.