Полное руководство по языку программирования С# 14 и платформе .NET 10 / Dynamic Language Runtime

DynamicObject и ExpandoObject

Начальный ⏱ 5 мин чтения 👁 2 🕓 обновлено 11.08.2026

Интересные возможности при разработке в C# и .NET с использованием DLR предоставляет пространство имен System.Dynamic и в частности класс ExpandoObject. Он позволяет создавать динамические объекты, наподобие тех, что используются в javascript:


// определяем объект, который будет хранять ряд значений
dynamic person = new System.Dynamic.ExpandoObject();
person.Name = "Tom";
person.Age = 46;
person.Languages = new List<string> { "english", "german", "french" };

Console.WriteLine($"{person.Name} - {person.Age}");
foreach (var lang in person.Languages)
    Console.WriteLine(lang);

// объявляем метод
person.IncrementAge = (Action<int>)(x => person.Age += x);
person.IncrementAge(6); // увеличиваем возраст на 6 лет
Console.WriteLine($"{person.Name} - {person.Age}");

Консольный вывод:


Tom - 46
english
german
french
Tom - 52

У динамического объекта ExpandoObject можно объявить любые свойства, например, Name, Age, Languages, которые могут представлять самые различные объекты. Кроме того, можно задать методы с помощью делегатов.

DynamicObject

На ExpandoObject по своему действию похож другой класс - DynamicObject. Он также позволяет задавать динамические объекты, но применяется в более изощренных и сложных ситуациях и когда необходим больший контроль над динамическими объектами. Тогда как ExpandoObject больше подходит для простых ситуаций, где не требуется определять какие-то специфические операции или статические компоненты.

Для использования DynamicObject надо создать свой класс, унаследовав его от DynamicObject и реализовав его методы:

  • TryBinaryOperation(): выполняет бинарную операцию между двумя объектами. Эквивалентно стандартным бинарным операциям, например, сложению x + y)

  • TryConvert(): выполняет преобразование к определенному типу. Эквивалентно базовому преобразованию в C#, например, (SomeType) obj

  • TryCreateInstance(): создает экземпляр объекта

  • TryDeleteIndex(): удаляет индексатор

  • TryDeleteMember(): удаляет свойство или метод

  • TryGetIndex(): получает элемент по индексу через индексатор. В C# может быть эквивалентно следующему выражению int x = collection[i]

  • TryGetMember(): получаем значение свойства. Эквивалентно обращению к свойству, например, string n = person.Name

  • TryInvoke(): вызов объекта в качестве делегата

  • TryInvokeMember(): вызов метода

  • TrySetIndex(): устанавливает элемент по индексу через индексатор. В C# может быть эквивалентно следующему выражению collection[i] = x;

  • TrySetMember(): устанавливает свойство. Эквивалентно присвоению свойству значения, например: person.Name = "Tom"

  • TryUnaryOperation(): выполняет унарную операцию подобно унарным операциям в C#: x++

Каждый из этих методов имеет одну и ту же модель определения: все они возвращают логическое значение, показывающее, удачно ли прошла операция. В качестве первого параметра все они принимают объект связывателя или binder. Если метод представляет вызов индексатора или метода объекта, которые могут принимать параметры, то в качестве второго параметра используется массив object[] - он хранит переданные в метод или индексатор аргументы.

Почти все операции, кроме установки и удаления свойств и индексаторов, возвращают определенное значение (например, если мы получаем значение свойства). В этом случае применяется третий параметр out object vaue, который предназначен для хранения возвращаемого объекта.

Например, определение метода TryInvokeMember():


public virtual bool TryInvokeMember (InvokeMemberBinder binder, object?[]? args, out object? result)

Параметр InvokeMemberBinder binder является связывателем - получает свойства и методы объекта, object?[]? args хранит передаваемые аргументы, out object? result предназначен для хранения выходного результата.

Рассмотрим на примере. Создадим класс динамического объекта:


using System.Dynamic;

class PersonObject : DynamicObject
{
    // словарь для хранения всех свойств
    Dictionary<string, object> members = new Dictionary<string, object>();

    // установка свойства
    public override bool TrySetMember(SetMemberBinder binder, object? value)
    {
        if(value is not null)
        {
            members[binder.Name] = value;
            return true;
        }
        return false;
    }
    // получение свойства
    public override bool TryGetMember(GetMemberBinder binder, out object? result)
    {
        result = null;
        if (members.ContainsKey(binder.Name))
        {
            result = members[binder.Name];
            return true;
        }
        return false;
    }
    // вызов метода
    public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args, out object? result)
    {
        result = null;
        if(args?[0] is int number)
        {
            // получаем метод по имен
            dynamic method = members[binder.Name];
            // вызываем метод, передавая его параметру значение args?[0]
            result = method(number);
        }
        // если result не равен null, то вызов метода прошел успешно
        return result != null;
    }
}

Класс наследуется от DynamicObject, так как непосредственно создавать объекты DynamicObject мы не можем. И также здесь переопределяется три унаследованных метода.

Для хранения всех членов класса, как свойств, так и методов, определен словарь Dictionary<string, object> members. Ключами здесь являются названия свойств и методов, а значениями - значения этих свойств.

В методе TrySetMember() производится установка свойства:


bool TrySetMember(SetMemberBinder binder, object? value)

Параметр binder хранит название устанавливаемого свойства (binder.Name), а value - значение, которое ему надо установить.

Для получения значения свойства переопределен метод TryGetMember:


bool TryGetMember(GetMemberBinder binder, out object? result)

Опять же binder содержит название свойства, а параметр result будет содержать значение получаемого свойства.

Для вызова методов определен метод TryInvokeMember:


public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args, out object? result)
{
    result = null;
    if(args?[0] is int number)
    {
        // получаем метод по имен
        dynamic method = members[binder.Name];
        // вызываем метод, передавая его параметру значение args?[0]
        result = method(number);
    }
    // если result не равен null, то вызов метода прошел успешно
    return result != null;
}

Сначала с помощью bindera получаем метод и затем передаем ему аргумент args[0], предварительно приведя его к типу int, и результат метода устанавливаем в параметре result. То есть в данном случае подразумевается, что метод будет принимать один параметр типа int и возвращать какой-то результат. Если метод возвращает true, то будем считать, что вызов метод прошел успешно.

Теперь применим класс в программе:


using System.Dynamic;

// создаем объект
dynamic person = new PersonObject();
// устанавливаем ряд свойств
person.Name = "Tom";
person.Age = 23;
// определяем метод для изменения свойства Age
Func<int, int> increment = (int n) => { person.Age += n; return person.Age; };
person.IncrementAge = increment;

Console.WriteLine($"{person.Name} - {person.Age}"); // Tom - 23
person.IncrementAge(4); // применяем метод
Console.WriteLine($"{person.Name} - {person.Age}"); // Tom - 27

Выражение person.Name = "Tom" будет вызывать метод TrySetMember, в который в качестве второго параметра будет передаваться строка "Tom".

Выражение return person.Age; вызывает метод TryGetMember.

Также у объекта person определен метод IncrementAge, который представляет действия лямбда-выражения (int n) => { person.Age += n; return person.Age; };. Это выражение принимает число n, увеличивает на это число свойство Age и возвращает новое значение person.Age. И при вызове этого метода будет происходить обращение к методу TryInvokeMember. И, таким образом, произойдет приращение значения свойства person.Age.

Проверь себя

📋 Тест по теме «DynamicObject и ExpandoObject» Тест по теме

Практические задания

Лёгкое Ручная проверка Заполните пропуск в коде (1)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

// определяем объект, который будет хранять ряд значений
_____ person = new System.Dynamic.ExpandoObject();
person.Name = "Tom";
person.Age = 46;
person.Languages = new List<string> { "english", "german", "french" };

Console.WriteLine($"{person.Name} - {person.Age}");
foreach (var lang in person.Languages)
    Console.WriteLine(lang);

// объявляем метод
person.IncrementAge = (Action<int>)(x => person.Age += x);
person.IncrementAge(6); // увеличиваем возраст на 6 лет
Console.WriteLine($"{person.Name} - {person.Age}");
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (2)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

Tom - _____
english
german
french
Tom - 52
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (3)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

public virtual bool _____ (InvokeMemberBinder binder, object?[]? args, out object? result)
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (4)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

using System.Dynamic;

class PersonObject : _____
{
    // словарь для хранения всех свойств
    Dictionary<string, object> members = new Dictionary<string, object>();

    // установка свойства
    public override bool TrySetMember(SetMemberBinder binder, object? value)
    {
        if(value is not null)
        {
            members[binder.Name] = value;
            return true;
        }
        return false;
    }
    // получение свойства
    public override bool TryGetMember(GetMemberBinder binder, out object? result)
    {
        result = null;
        if (members.ContainsKey(binder.Name))
        {
            result = members[binder.Name];
            return true;
        }
        return false;
    }
    // вызов метода
    public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args, out object? result)
    {
        result = null;
        if(args?[0] is int number)
        {
            // получаем метод по имен
            dynamic method = members[binder.Name];
            // вызываем метод, передавая его параметру значение args?[0]
            result = method(number);
        }
        // если result не равен null, то вызов метода прошел успешно
        return result != null;
    }
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (5)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

bool TrySetMember(SetMemberBinder binder, _____? value)
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (1)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

// определяем объект, который будет хранять ряд значений
dynamic person = new System.Dynamic.ExpandoObject();
person.Name = "Tom";
person._____ = 46;
person.Languages = new List<string> { "english", "_____", "french" };

Console.WriteLine($"{person.Name} - {person.Age}");
foreach (var lang in person.Languages)
    Console.WriteLine(lang);

// объявляем метод
person.IncrementAge = (Action<int>)(x => person.Age += x);
person.IncrementAge(6); // увеличиваем возраст на 6 лет
Console.WriteLine($"{person.Name} - {person.Age}");
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (2)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

Tom - _____
english
german
french
Tom - 52
Войдите, чтобы отправить решение
Среднее Ручная проверка Расставьте строки кода в правильном порядке
+20 XP

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

english
Tom - 52
french
Tom - 46
german
Войдите, чтобы отправить решение
Сложное Ручная проверка Объясните и модифицируйте пример из урока «DynamicObject и ExpandoObject»
+30 XP

Опишите своими словами, что делает приведённый пример кода из урока «DynamicObject и ExpandoObject», и предложите одно осмысленное изменение (например, другое входное значение или дополнительное условие). Приведите итоговый код.

// определяем объект, который будет хранять ряд значений
dynamic person = new System.Dynamic.ExpandoObject();
person.Name = "Tom";
person.Age = 46;
person.Languages = new List<string> { "english", "german", "french" };

Console.WriteLine($"{person.Name} - {person.Age}");
foreach (var lang in person.Languages)
    Console.WriteLine(lang);

// объявляем метод
person.IncrementAge = (Action<int>)(x => person.Age += x);
person.IncrementAge(6); // увеличиваем возраст на 6 лет
Console.WriteLine($"{person.Name} - {person.Age}");
Войдите, чтобы отправить решение
Сложное Ручная проверка Найдите потенциальную проблему в коде урока «DynamicObject и ExpandoObject»
+30 XP

Изучите приведённый фрагмент кода и опишите, при каких входных данных или условиях он может сработать не так, как ожидается (граничный случай, отсутствие проверки и т.п.). Предложите исправленный вариант.

Tom - 46
english
german
french
Tom - 52
Войдите, чтобы отправить решение
Мини-проект Ручная проверка Мини-проект по теме «DynamicObject и ExpandoObject»
+50 XP

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

Войдите, чтобы отправить решение
Домашнее задание Ручная проверка Домашнее задание по теме «DynamicObject и ExpandoObject»
+25 XP

Напишите собственную небольшую программу, применяющую материал темы «DynamicObject и ExpandoObject». Используйте не менее одной конструкции, показанной в примерах урока. Прикрепите код решения и краткое описание того, что он делает.

Войдите, чтобы отправить решение

Обсуждение (0)

Войдите, чтобы оставить комментарий.