1. Введение
C# — представитель строготипизированных языков. Это значит, что на этапе компиляции переменная должна иметь определённый тип, и обращаться к членам класса можно только в соответствии с этим типом. Это безопасно, удобно, понятно IDE и компилятору.
Но иногда требуется обращаться к объектам, структура которых может быть неизвестна на этапе компиляции — например, когда:
- Исполняется код, написанный на другом языке .NET (например IronPython или IronRuby), где типизация динамическая.
- При работе с COM-объектами (например, с Microsoft Office через Interop).
- При взаимодействии с динамическими структурами данных: например, слабоформализованными JSON-объектами.
- Не хочется писать кучу шаблонного кода, а проще “поверить” объекту на слово и попробовать вызвать у него нужный метод по имени.
С появлением в C# 4.0 ключевого слова dynamic и платформы DLR появилась возможность писать код, который ведёт себя “как в Python, только в C#”: проверка будет на этапе выполнения, а не во время компиляции. Через это волшебное слово можно заставить компилятор быть менее строгим и вручную проверить, есть ли нужный метод/свойство только когда программа реально дойдёт до этого места.
Что такое DLR?
Dynamic Language Runtime (DLR) — это инфраструктурное расширение к CLR (Common Language Runtime), которое позволяет реализовать поддержку динамических языков (и динамического поведения в статических языках). DLR берёт на себя выполнение операций, которые не могут быть проверены компилятором: например, вызов метода по имени, если тип станет известен только во время работы программы. Благодаря DLR, C# получает "динамический режим".
2. Ключевое слово dynamic: как оно работает
Отличие от object
Некоторые новички думают, что dynamic — это просто более модный способ написать object и каждый раз делать приведение. Это не так.
- Переменная типа object всегда требует явного приведения к конкретному типу для вызова методов (и компилятор это проверяет).
- Переменная типа dynamic даёт команду компилятору: “поверь мне, я сам знаю, какой там будет тип — разберёмся на месте!”. Если метода не существует — будет исключение во время выполнения.
Пример:
// Пример с object
object obj = "Привет, C#!";
// obj.ToUpper(); // Ошибка компиляции! Компилятор не знает, что obj — строка
string s = ((string)obj).ToUpper(); // Только так
// Пример с dynamic
dynamic dyn = "Привет, C#!";
string upper = dyn.ToUpper(); // Компилятор не возражает, проверка будет в рантайме
С dynamic можно обращаться к несуществующим свойствам и методам. Если их нет — будет исключение типа RuntimeBinderException.
Использование в примере приложения
Допустим, в нашем мини-приложении есть блок работы с JSON-данными, структура которых может меняться на лету. С dynamic можно обращаться к свойствам, будто они всегда существуют:
dynamic user = new System.Dynamic.ExpandoObject();
user.Name = "Вася";
user.Age = 35;
Console.WriteLine($"Имя: {user.Name}, возраст: {user.Age}");
3. DLR в действии: что происходит под капотом
Когда переменная объявляется как dynamic, компилятор C# запоминает, что любые обращения к методам, свойствам, индексаторам, операторам и т.д. над этим объектом он не может проверить заранее.
Вместо этого на месте любого динамического вызова вставляется код обращения к системному “свободному диспетчеру”. DLR наблюдает: а существует ли на самом деле тот метод или свойство, к которому мы пространно, небрежно и с оптимизмом обращаемся? Если да — выполняет вызов, если нет — кинет исключение.
Визуальная схема
+------------------------+
| Код с dynamic |
+----------+-------------+
|
v
+----------+-------------+
| Компилятор C# вставляет|
| "запрос к DLR" |
+----------+-------------+
|
v
+----------+-------------+
| В рантайме DLR ищет |
| метод/свойство |
+----------+-------------+
|
/---------\
| |
найден не найден
| |
v v
вызов исключение
метода (RuntimeBinderException)
DLR заботится о поиске методов и свойств в объекте — и даже кэширует часто используемые динамические вызовы!
Немного истории
DLR создавался для поддержки языков, которые изначально динамические: IronPython, IronRuby, а также для облегчения интеграции с динамическими объектами в C#. Сегодня это активно используется при работе с объектами типа ExpandoObject, DynamicObject, а также с COM и динамически сериализованными данными.
4. Сравнение: static vs dynamic
Давайте сравним, как выглядят вызовы свойств и методов в статической и динамической парадигме:
| Способ | Объявление | Проверка времени компиляции | Поведение при ошибке |
|---|---|---|---|
|
|
Требует каст к нужному типу | Ошибка компиляции или InvalidCastException |
|
|
Нет, всё в рантайме | RuntimeBinderException, если метода нет |
| статический тип | |
Проверяется компилятором | Ошибка компиляции |
Пример для наглядности:
// Статическая типизация
MyUser u = new MyUser();
u.PrintInfo(); // Проверяется компилятором
// dynamic
dynamic du = new MyUser();
du.PrintInfo(); // Компилятор не проверяет, найдёт метод только в рантайме
// object
object ou = new MyUser();
// ou.PrintInfo(); // Ошибка компиляции
((MyUser)ou).PrintInfo(); // Нужно явно привести к типу
Статические методы и dynamic
dynamic d = "Hello";
Console.WriteLine(d.Length); // 5 — всё хорошо
Console.WriteLine(d.FakeMethod()); // RuntimeBinderException во время исполнения
Но стоит быть осторожным! Если вы опечатались или имя метода не существует — это проявится только во время работы приложения. Классический пример "ошибки на продакшене”.
5. Практические примеры: динамика в реальных задачах
Работа с ExpandoObject
using System.Dynamic;
dynamic person = new ExpandoObject();
person.Name = "Сергей";
person.Greet = (Action)(() => Console.WriteLine($"Привет, {person.Name}!"));
person.Greet(); // Выведет: Привет, Сергей!
Здесь мы на лету добавили свойство Name и даже делегат Greet. Очень похоже на работу с объектами в JavaScript.
Привязка к структурам JSON
using System.Dynamic;
using Newtonsoft.Json;
string json = @"{""name"": ""Андрей"", ""age"": 30}";
dynamic obj = JsonConvert.DeserializeObject
(json); Console.WriteLine(obj.name); // Андрей Console.WriteLine(obj.age); // 30
Обратите внимание: если написать obj.surname — будет ошибка уже во время выполнения.
COM-Interop (Office Automation)
dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
excel.Visible = true; // “Видим” свойство, хотя компилятор не знает его типа
6. Полезные нюансы
DLR, dynamic и использование с другими .NET-языками
- IronPython/IronRuby: можно инстанцировать объекты этих языков и вызывать их методы из C# через dynamic.
- COM-объекты: автоматическая обёртка методов и свойств через dynamic.
- Кастомные динамические объекты: реализуя интерфейс IDynamicMetaObjectProvider, можно сделать свой класс с динамическими вызовами.
Пример интеграции с IronPython
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
ScriptEngine engine = Python.CreateEngine();
dynamic py = engine.Execute("class Test: pass\nTest()");
py.x = 42;
Console.WriteLine(py.x); // 42
Как реализуются свои динамические типы
Обычно проще унаследоваться от DynamicObject.
using System.Dynamic;
class MyDynamic : DynamicObject
{
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == "Ping")
{
result = "Pong!";
return true;
}
result = null;
return false;
}
}
dynamic d = new MyDynamic();
Console.WriteLine(d.Ping); // Pong!
Console.WriteLine(d.Miss); // null (не выбрасывает исключение)
Когда dynamic — вредно
- Снижает производительность (динамические вызовы медленнее статических).
- Усложняет отладку и поддержку.
- Лишает большинства плюшек IDE: автодополнения, рефакторинга, поиска по использованию.
- Может привести к ошибкам, которые обнаруживаются только у пользователя или на сервере.
Когда стоит реально использовать dynamic
- Интеграция с внешними библиотеками, где невозможно или нецелесообразно писать обёртки (COM, Python, JavaScript).
- Манипулирование слабо типизированными структурами (JSON/XML/ExpandoObject).
- Для написания фреймворков, метапрограммирования и гибких API, которые должны быть динамическими по замыслу.
Во всех других случаях лучше использовать статические типы и радоваться помощи компилятора :-)
7. Типичные ошибки при работе с dynamic и DLR
Ошибка №1: Обращение к несуществующим членам.
Вызов несуществующего метода или свойства для dynamic приводит к RuntimeBinderException в рантайме, что сложно отловить на этапе разработки.
Ошибка №2: Игнорирование проверки типов.
Без проверки типов перед вызовом (например, if (obj is IDictionary<string, object>)) можно получить неожиданные ошибки в рантайме.
Ошибка №3: Использование dynamic в публичных API.
Динамические типы усложняют поддержку кода, так как IDE и анализаторы не могут подсказать правильные члены, что снижает читаемость.
Ошибка №4: Злоупотребление dynamic в производительном коде.
Динамические вызовы медленнее статических и увеличивают накладные расходы. Используйте dynamic только при необходимости.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ