JavaRush /Курси /C# SELF /Динамічні типи ( dynamic

Динамічні типи ( dynamic) і DLR

C# SELF
Рівень 63 , Лекція 2
Відкрита

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

Порівняймо, як виглядають виклики властивостей і методів у статичній та динамічній парадигмах:

Спосіб Оголошення Перевірка під час компіляції Поведінка при помилці
object
object obj = ...
Потребує явного перетворення до потрібного типу Помилка компіляції або InvalidCastException
dynamic
dynamic dyn = ...
Ні, усе під час виконання RuntimeBinderException, якщо методу немає
статичний тип
MyClass a = ...
Перевіряється компілятором Помилка компіляції

Приклад для наочності:


// Статична типізація
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 лише за потреби.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ