Пошаговое руководство. Создание и использование динамических объектов в C#

Динамические объекты предоставляют такие элементы, как свойства и методы, во время выполнения, а не во время компиляции. Динамические объекты позволяют создавать объекты для работы со структурами, которые не соответствуют статичным типам или формату. Например, можно использовать динамический объект для ссылки на модель DOM HTML, которая может содержать любую комбинацию допустимых элементов и атрибутов разметки HTML. Поскольку каждый документ HTML является уникальным, элементы для конкретного документа HTML определяются во время выполнения. Наиболее распространенный способ ссылки на атрибут элемента HTML заключается в передаче имени этого атрибута в метод GetProperty элемента. Для ссылки на атрибут id элемента HTML <div id="Div1"> следует сначала получить ссылку на элемент <div>, а затем использовать divElement.GetProperty("id"). При использовании динамического объекта можно сослаться на атрибут id в виде divElement.id.

Динамические объекты обеспечивают удобный доступ к динамическим языкам, таким как IronPython и IronRuby. Динамический объект можно использовать для ссылки на динамический скрипт, интерпретируемый во время выполнения.

Ссылка на динамический объект выполняется с помощью позднего связывания. Тип объекта с поздней привязкой указывается как dynamic. Дополнительные сведения см. в динамической версии.

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

Эта статья содержит два независимых пошаговых руководства.

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

Необходимые компоненты

Примечание.

Отображаемые на компьютере имена или расположения некоторых элементов пользовательского интерфейса Visual Studio могут отличаться от указанных в следующих инструкциях. Это зависит от имеющегося выпуска Visual Studio и используемых параметров. Дополнительные сведения см. в разделе Персонализация среды IDE.

  • Для второго пошагового руководства установите IronPython для .NET. Перейдите на страницу загрузки для получения последней версии.

Создание пользовательского динамического объекта

В первом пошаговом руководстве определяется пользовательский динамический объект, выполняющий поиск по содержимому текстового файла. Динамическое свойство указывает искомый текст. Например, если в вызывающем коде указано dynamicFile.Sample, динамический класс возвращает общий список строк, содержащий все строки из файла, которые начинаются со слова "Sample". При поиске не учитывается регистр. Динамический класс также поддерживает два дополнительных аргумента. Первый аргумент — это значение перечисления параметра поиска, задающее, где динамический класс должен искать соответствия: в начале строки, в конце строки или в любом месте строки. Второй аргумент задает, что динамический класс должен перед поиском отсекать начальные и конечные пробелы в каждой строке. Например, если в вызывающем коде указано dynamicFile.Sample(StringSearchOption.Contains), динамический класс выполняет поиск слова "Sample" в любом месте строки. Если вызывающий код указывает dynamicFile.Sample(StringSearchOption.StartsWith, false), динамический класс выполняет поиск "Sample" в начале каждой строки и не удаляет начальные и конечные пробелы. По умолчанию динамический класс выполняет поиск соответствия в начале каждой строки, предварительно удаляя начальные и конечные пробелы.

Создание настраиваемого динамического класса

Запустите среду Visual Studio. Выберите Создать новый проект. В диалоговом окне "Создание проекта" выберите C#, выберите консольное приложение и нажмите кнопку "Далее". В диалоговом окне Настройка нового проекта введите значение DynamicSample для параметра Имя проекта и нажмите кнопку Далее. В диалоговом окне "Дополнительные сведения" выберите .NET 7.0 (Current) для целевой платформы и нажмите кнопку "Создать". В обозревателе решений щелкните проект DynamicSample правой кнопкой мыши и выберите Добавить>Класс. В поле Имя введите ReadOnlyFile, а затем нажмите кнопку Добавить. В верхней части файла ReadOnlyFile.cs илиReadOnlyFile.vb добавьте следующий код для импорта пространств имен System.IO и System.Dynamic.

using System.IO;
using System.Dynamic;

Пользовательский динамический объект использует перечисление для определения условия поиска. Перед оператором класса добавьте следующее определение перечисления.

public enum StringSearchOption
{
    StartsWith,
    Contains,
    EndsWith
}

Обновите оператор класса, чтобы он наследовал класс DynamicObject, как показано в следующем примере кода.

class ReadOnlyFile : DynamicObject

Добавьте в класс ReadOnlyFile следующий код, чтобы задать закрытое поле для пути к файлу и конструктор для класса ReadOnlyFile.

// Store the path to the file and the initial line count value.
private string p_filePath;

// Public constructor. Verify that file exists and store the path in
// the private variable.
public ReadOnlyFile(string filePath)
{
    if (!File.Exists(filePath))
    {
        throw new Exception("File path does not exist.");
    }

    p_filePath = filePath;
}
  1. Добавьте приведенный ниже метод GetPropertyValue в класс ReadOnlyFile. Метод GetPropertyValue принимает в качестве входных данных условие поиска и возвращает строки текстового файла, соответствующие этому условию. Динамический метод, предоставленный классом ReadOnlyFile, вызывает метод GetPropertyValue для извлечения соответствующих результатов.
public List<string> GetPropertyValue(string propertyName,
                                     StringSearchOption StringSearchOption = StringSearchOption.StartsWith,
                                     bool trimSpaces = true)
{
    StreamReader sr = null;
    List<string> results = new List<string>();
    string line = "";
    string testLine = "";

    try
    {
        sr = new StreamReader(p_filePath);

        while (!sr.EndOfStream)
        {
            line = sr.ReadLine();

            // Perform a case-insensitive search by using the specified search options.
            testLine = line.ToUpper();
            if (trimSpaces) { testLine = testLine.Trim(); }

            switch (StringSearchOption)
            {
                case StringSearchOption.StartsWith:
                    if (testLine.StartsWith(propertyName.ToUpper())) { results.Add(line); }
                    break;
                case StringSearchOption.Contains:
                    if (testLine.Contains(propertyName.ToUpper())) { results.Add(line); }
                    break;
                case StringSearchOption.EndsWith:
                    if (testLine.EndsWith(propertyName.ToUpper())) { results.Add(line); }
                    break;
            }
        }
    }
    catch
    {
        // Trap any exception that occurs in reading the file and return null.
        results = null;
    }
    finally
    {
        if (sr != null) {sr.Close();}
    }

    return results;
}

После метода GetPropertyValue добавьте следующий код, чтобы переопределить метод TryGetMember класса DynamicObject. Метод TryGetMember вызывается при запросе члена динамического класса без указания аргументов. Аргумент binder содержит сведения об элементе, на который дается ссылка, а аргумент result ссылается на результат, возвращенный для указанного элемента. Метод TryGetMember возвращает логическое значение true, если запрошенный элемент существует. В противном случае возвращается false.

// Implement the TryGetMember method of the DynamicObject class for dynamic member calls.
public override bool TryGetMember(GetMemberBinder binder,
                                  out object result)
{
    result = GetPropertyValue(binder.Name);
    return result == null ? false : true;
}

После метода TryGetMember добавьте следующий код, чтобы переопределить метод TryInvokeMember класса DynamicObject. Метод TryInvokeMember вызывается при запросе члена динамического класса с аргументами. Аргумент binder содержит сведения об элементе, на который дается ссылка, а аргумент result ссылается на результат, возвращенный для указанного элемента. Аргумент args содержит массив аргументов, передаваемых в элемент. Метод TryInvokeMember возвращает логическое значение true, если запрошенный элемент существует. В противном случае возвращается false.

Пользовательская версия метода TryInvokeMember ожидает, что первый аргумент будет значением из перечисления StringSearchOption, заданного на предыдущем шаге. Метод TryInvokeMember ожидает, что второй аргумент будет логическим значением. Если один или оба аргумента являются допустимыми значениями, они передаются методу GetPropertyValue для получения результатов.

// Implement the TryInvokeMember method of the DynamicObject class for
// dynamic member calls that have arguments.
public override bool TryInvokeMember(InvokeMemberBinder binder,
                                     object[] args,
                                     out object result)
{
    StringSearchOption StringSearchOption = StringSearchOption.StartsWith;
    bool trimSpaces = true;

    try
    {
        if (args.Length > 0) { StringSearchOption = (StringSearchOption)args[0]; }
    }
    catch
    {
        throw new ArgumentException("StringSearchOption argument must be a StringSearchOption enum value.");
    }

    try
    {
        if (args.Length > 1) { trimSpaces = (bool)args[1]; }
    }
    catch
    {
        throw new ArgumentException("trimSpaces argument must be a Boolean value.");
    }

    result = GetPropertyValue(binder.Name, StringSearchOption, trimSpaces);

    return result == null ? false : true;
}

Сохранить и закрыть файл.

Создание примера текстового файла

В обозревателе решений щелкните проект DynamicSample правой кнопкой мыши и выберите Добавить>Новый элемент. На панели Установленные шаблоны выберите Общие, а затем шаблон Текстовый файл. Оставьте имя по умолчанию TextFile1.txt в поле "Имя ", а затем нажмите кнопку "Добавить". Скопируйте в файл TextFile1.txt следующий текст.

List of customers and suppliers

Supplier: Lucerne Publishing (https://www.lucernepublishing.com/)
Customer: Preston, Chris
Customer: Hines, Patrick
Customer: Cameron, Maria
Supplier: Graphic Design Institute (https://www.graphicdesigninstitute.com/)
Supplier: Fabrikam, Inc. (https://www.fabrikam.com/)
Customer: Seubert, Roxanne
Supplier: Proseware, Inc. (http://www.proseware.com/)
Customer: Adolphi, Stephan
Customer: Koch, Paul

Сохранить и закрыть файл.

Создание примера приложения, использующего пользовательский динамический объект

В Обозреватель решений дважды щелкните файл Program.cs. Добавьте следующий код в процедуру Main, чтобы создать экземпляр класса ReadOnlyFile для файла TextFile1.txt. В этом коде используется позднее связывание для вызова динамических элементов и извлечения строк текста, которые содержат строку "Customer".

dynamic rFile = new ReadOnlyFile(@"..\..\..\TextFile1.txt");
foreach (string line in rFile.Customer)
{
    Console.WriteLine(line);
}
Console.WriteLine("----------------------------");
foreach (string line in rFile.Customer(StringSearchOption.Contains, true))
{
    Console.WriteLine(line);
}

Сохраните файл и нажмите клавиши CTRL+F5 для сборки и запуска приложения.

Вызов библиотеки динамического языка

В следующем пошаговом руководстве создается проект, который обращается к библиотеке, написанной на динамическом языке IronPython.

Создание пользовательского динамического класса

В Visual Studio выберите Файл>Создать>Проект. В диалоговом окне "Создание проекта" выберите C#, выберите консольное приложение и нажмите кнопку "Далее". В диалоговом окне Настройка нового проекта введите значение DynamicIronPythonSample для параметра Имя проекта и нажмите кнопку Далее. В диалоговом окне "Дополнительные сведения" выберите .NET 7.0 (Current) для целевой платформы и нажмите кнопку "Создать". Установите пакет NuGet IronPython. Измените файл Program.cs . В верхней части файла добавьте следующий код для импорта пространств имен Microsoft.Scripting.Hosting и IronPython.Hosting из библиотек IronPython и пространства имен System.Linq.

using System.Linq;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;

В методе Main добавьте следующий код, чтобы создать объект Microsoft.Scripting.Hosting.ScriptRuntime, в котором будут размещены библиотеки IronPython. Объект ScriptRuntime загружает модуль библиотеки IronPython random.py.

// Set the current directory to the IronPython libraries.
System.IO.Directory.SetCurrentDirectory(
   Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) +
   @"\IronPython 2.7\Lib");

// Create an instance of the random.py IronPython library.
Console.WriteLine("Loading random.py");
ScriptRuntime py = Python.CreateRuntime();
dynamic random = py.UseFile("random.py");
Console.WriteLine("random.py loaded.");

После указания в коде необходимости загрузки модуля random.py добавьте следующий код, чтобы создать массив целых чисел. Массив передается методу shuffle модуля random.py, который произвольно сортирует значения в массиве.

// Initialize an enumerable set of integers.
int[] items = Enumerable.Range(1, 7).ToArray();

// Randomly shuffle the array of integers by using IronPython.
for (int i = 0; i < 5; i++)
{
    random.shuffle(items);
    foreach (int item in items)
    {
        Console.WriteLine(item);
    }
    Console.WriteLine("-------------------");
}

Сохраните файл и нажмите клавиши CTRL+F5 для сборки и запуска приложения.

См. также