Пошаговое руководство. Создание и использование динамических объектов (C# и Visual Basic)
Динамические объекты предоставляют такие элементы, как свойства и методы, во время выполнения, а не во время компиляции. Это позволяет создавать объекты для работы со структурами, не соответствующими статическому типу или формату. Например, можно использовать динамический объект для ссылки на модель DOM HTML, которая может содержать любую комбинацию допустимых элементов и атрибутов разметки HTML. Поскольку каждый документ HTML является уникальным, элементы для конкретного документа HTML определяются во время выполнения. Наиболее распространенный способ ссылки на атрибут элемента HTML состоит в передаче имени этого атрибута в метод GetProperty элемента. Для ссылки на атрибут id элемента HTML <div id="Div1"> следует сначала получить ссылку на элемент <div>, а затем указать divElement.GetProperty("id"). При использовании динамического объекта можно сослаться на атрибут id в виде divElement.id.
Динамические объекты обеспечивают удобный доступ к динамическим языкам, таким как IronPython и IronRuby. С помощью динамического объекта можно ссылаться на динамический скрипт, интерпретируемый во время выполнения.
Ссылка на динамический объект выполняется с помощью позднего связывания. В C# тип объектов с поздним связыванием указывается как dynamic. В Visual Basic тип объектов с поздним связыванием указывается как Object. Дополнительные сведения см. в разделах dynamic (Справочник по C#) и Раннее и позднее связывание (Visual Basic).
Существует возможность создания пользовательских динамических объектов с помощью классов в пространстве имен System.Dynamic. Например, можно создать объект ExpandoObject и задать элементы этого объекта во время выполнения. Также можно создать собственный тип, наследующий класс DynamicObject. Затем для обеспечения динамических функциональных возможностей во время выполнения можно переопределить элементы класса DynamicObject.
В данном пошаговом руководстве демонстрируется выполнение следующих задач:
создание пользовательского объекта, который динамически предоставляет содержимое текстового файла в виде свойств объекта;
Создайте проект, использующий библиотеку IronPython.
Обязательные компоненты
Для выполнения инструкций этого пошагового руководства потребуется установить IronPython 2.6.1 для .NET 4.0. IronPython 2.6.1 для .NET 4.0 можно загрузить со страницы CodePlex.
Примечание
На вашем компьютере названия некоторых элементов интерфейса пользователя Visual Studio или их расположение могут отличаться от указанных в нижеследующих инструкциях. Это зависит от имеющегося выпуска Visual Studio и используемых параметров. Дополнительные сведения см. в разделе Параметры Visual Studio.
Создание пользовательского динамического объекта
В первом проекте, создаваемом в данном пошаговом руководстве, определяется пользовательский динамический объект, выполняющий поиск по содержимому текстового файла. Текст для поиска задается именем динамического свойства. Например, при вызове кода, в котором указано dynamicFile.Sample, динамический класс возвращает общий список строк, содержащий все строки файла, начинающиеся со слова "Sample". При поиске регистр не учитывается. Динамический класс также поддерживает два дополнительных аргумента. Первый аргумент — это значение перечисления параметра поиска, задающее, где динамический класс должен искать соответствия: в начале строки, в конце строки или в любом месте строки. Второй аргумент задает, что динамический класс должен перед поиском отсекать начальные и конечные пробелы в каждой строке. Например, если в вызывающем коде указано dynamicFile.Sample(StringSearchOption.Contains), то динамический класс выполняет поиск слова "Sample" в любом месте строки. Если в вызывающем коде указано dynamicFile.Sample(StringSearchOption.StartsWith, false), то динамический класс выполняет поиск слова "Sample" в начале каждой строки и не удаляет начальные и конечные пробелы в строках. По умолчанию динамический класс выполняет поиск соответствия в начале каждой строки, предварительно удаляя начальные и конечные пробелы.
Создание пользовательского динамического класса
Запустите Visual Studio.
В меню Файл выберите пункт Создать, а затем команду Проект.
Убедитесь, что в диалоговом окне Новый проект в области Типы проектов выбран пункт Windows. В области Шаблоны выберите пункт Консольное приложение. В поле Имя введите DynamicSample и нажмите кнопку ОК. Новый проект создан.
Щелкните правой кнопкой мыши проект DynamicSample, выберите команду Добавить и нажмите пункт Класс. В поле Имя введите ReadOnlyFile и нажмите кнопку ОК. Добавится новый файл, содержащий класс ReadOnlyFile.
Вверху файла ReadOnlyFile.cs или ReadOnlyFile.vb добавьте следующий код для импорта пространств имен System.IO и System.Dynamic.
Imports System.IO Imports System.Dynamic
using System.IO; using System.Dynamic;
Пользовательский динамический объект использует перечисление для определения условия поиска. Перед оператором класса добавьте следующее определение перечисления.
Public Enum StringSearchOption StartsWith Contains EndsWith End Enum
public enum StringSearchOption { StartsWith, Contains, EndsWith }
Обновите оператор класса, чтобы он наследовал класс DynamicObject, как показано в следующем примере кода.
Public Class ReadOnlyFile Inherits DynamicObject
class ReadOnlyFile : DynamicObject
Добавьте в класс ReadOnlyFile следующий код, чтобы задать закрытое поле для пути к файлу и конструктор для класса ReadOnlyFile.
' Store the path to the file and the initial line count value. Private p_filePath As String ' Public constructor. Verify that file exists and store the path in ' the private variable. Public Sub New(ByVal filePath As String) If Not File.Exists(filePath) Then Throw New Exception("File path does not exist.") End If p_filePath = filePath End Sub
// 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; }
Добавьте в класс ReadOnlyFile следующий метод GetPropertyValue. Метод GetPropertyValue принимает в качестве ввода условие поиска и возвращает строки текстового файла, соответствующие этому условию. Динамический метод, предоставленный классом ReadOnlyFile, вызывает метод GetPropertyValue для извлечения соответствующих результатов.
Public Function GetPropertyValue(ByVal propertyName As String, Optional ByVal StringSearchOption As StringSearchOption = StringSearchOption.StartsWith, Optional ByVal trimSpaces As Boolean = True) As List(Of String) Dim sr As StreamReader = Nothing Dim results As New List(Of String) Dim line = "" Dim testLine = "" Try sr = New StreamReader(p_filePath) While Not sr.EndOfStream line = sr.ReadLine() ' Perform a case-insensitive search by using the specified search options. testLine = UCase(line) If trimSpaces Then testLine = Trim(testLine) Select Case StringSearchOption Case StringSearchOption.StartsWith If testLine.StartsWith(UCase(propertyName)) Then results.Add(line) Case StringSearchOption.Contains If testLine.Contains(UCase(propertyName)) Then results.Add(line) Case StringSearchOption.EndsWith If testLine.EndsWith(UCase(propertyName)) Then results.Add(line) End Select End While Catch ' Trap any exception that occurs in reading the file and return Nothing. results = Nothing Finally If sr IsNot Nothing Then sr.Close() End Try Return results End Function
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 Overrides Function TryGetMember(ByVal binder As GetMemberBinder, ByRef result As Object) As Boolean result = GetPropertyValue(binder.Name) Return If(result Is Nothing, False, True) End Function
// 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 Overrides Function TryInvokeMember(ByVal binder As InvokeMemberBinder, ByVal args() As Object, ByRef result As Object) As Boolean Dim StringSearchOption As StringSearchOption = StringSearchOption.StartsWith Dim trimSpaces = True Try If args.Length > 0 Then StringSearchOption = CType(args(0), StringSearchOption) Catch Throw New ArgumentException("StringSearchOption argument must be a StringSearchOption enum value.") End Try Try If args.Length > 1 Then trimSpaces = CType(args(1), Boolean) Catch Throw New ArgumentException("trimSpaces argument must be a Boolean value.") End Try result = GetPropertyValue(binder.Name, StringSearchOption, trimSpaces) Return If(result Is Nothing, False, True) End Function
// 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 (http://www.lucernepublishing.com/) Customer: Preston, Chris Customer: Hines, Patrick Customer: Cameron, Maria Supplier: Graphic Design Institute (http://www.graphicdesigninstitute.com/) Supplier: Fabrikam, Inc. (http://www.fabrikam.com/) Customer: Seubert, Roxanne Supplier: Proseware, Inc. (https://www.proseware.com/) Customer: Adolphi, Stephan Customer: Koch, Paul
Сохраните и закройте файл.
Создание примера приложения, в котором применяется пользовательский динамический объект
В обозревателе решений дважды щелкните файл Module1.vb, если используется Visual Basic, или файл Program.cs, если используется Visual C#.
Добавьте следующий код в процедуру Main, чтобы создать экземпляр класса ReadOnlyFile для файла TextFile1.txt. В этом коде используется позднее связывание для вызова динамических элементов и извлечения строк текста, содержащих последовательность символов "Customer".
Dim rFile As Object = New ReadOnlyFile("..\..\TextFile1.txt") For Each line In rFile.Customer Console.WriteLine(line) Next Console.WriteLine("----------------------------") For Each line In rFile.Customer(StringSearchOption.Contains, True) Console.WriteLine(line) Next
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. Перед созданием проекта необходимо установить IronPython 2.6.1 для .NET 4.0. IronPython 2.6.1 для .NET 4.0 можно загрузить со страницы CodePlex.
Создание пользовательского динамического класса
В Visual Studio в меню Файл последовательно выберите пункты Создать и Проект.
Убедитесь, что в диалоговом окне Новый проект в области Типы проектов выбран пункт Windows. В области Шаблоны выберите пункт Консольное приложение. В поле Имя введите DynamicIronPythonSample и нажмите кнопку ОК. Новый проект создан.
Если используется Visual Basic, щелкните правой кнопкой мыши проект DynamicIronPythonSample и выберите пункт Свойства. Перейдите на вкладку Ссылки. Нажмите кнопку Добавить. Если используется Visual C#, в обозревателе решений щелкните правой кнопкой мыши папку Ссылки и выберите пункт Добавить ссылку.
На вкладке Обзор перейдите к папке, в которой установлены библиотеки IronPython. Например, C:\Program Files\IronPython 2.6 for .NET 4.0. Выберите библиотеки IronPython.dll, IronPython.Modules.dll, Microsoft.Scripting.dll и Microsoft.Dynamic.dll. Нажмите кнопку ОК.
Если используется Visual Basic, отредактируйте файл Module1.vb. Если используется Visual C#, отредактируйте файл Program.cs.
В верхней части файла добавьте следующий код для импорта пространств имен Microsoft.Scripting.Hosting и IronPython.Hosting из библиотек IronPython.
Imports Microsoft.Scripting.Hosting Imports IronPython.Hosting
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. My.Computer.FileSystem.CurrentDirectory = My.Computer.FileSystem.SpecialDirectories.ProgramFiles & "\IronPython 2.6 for .NET 4.0\Lib" ' Create an instance of the random.py IronPython library. Console.WriteLine("Loading random.py") Dim py = Python.CreateRuntime() Dim random As Object = py.UseFile("random.py") Console.WriteLine("random.py loaded.")
// Set the current directory to the IronPython libraries. System.IO.Directory.SetCurrentDirectory( Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\IronPython 2.6 for .NET 4.0\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. Dim items = Enumerable.Range(1, 7).ToArray() ' Randomly shuffle the array of integers by using IronPython. For i = 0 To 4 random.shuffle(items) For Each item In items Console.WriteLine(item) Next Console.WriteLine("-------------------") Next
// 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 для построения и выполнения приложения.
См. также
Ссылки
Основные понятия
Раннее и позднее связывание (Visual Basic)