Опубликован: 25.07.2012 | Уровень: специалист | Доступ: платный
Лекция 7:

Генерация объектно-ориентированного кода. Технология CodeDom

< Лекция 6 || Лекция 7: 123 || Лекция 8 >
Аннотация: Изучается генерация объектно-ориентированного кода с применением технологии CodeDom. Рассматриваются инициализация пространств имен, создание классов, методов, переменных, массивов, циклов и так далее. Даются примеры сгенерированного кода на языках C# и Visual Basic. Изучается автоматическая компиляции кода, приводятся преимущества и недостатки применения технологии CodeDom.

Объектная модель документов кода

В данной лекции мы рассмотрим еще одну технологию Microsoft для генерации кода - CodeDom (Code Document Object Model). На русский язык это название переводится как объектная модель документов кода. Технология CodeDom позволяет строить модель кода, используя набор объектов. Впоследствии провайдер кода может конвертировать эту модель в программный код на конкретном языке, вроде C# или Visual Basic или другой язык из семейства языков .Net. Применение CodeDom отличается от применения шаблонов или программ, напрямую манипулирующих текстом для генерации кода. Пространство имен CodeDom является набором классов, представляющих понятия объектно-ориентированных языков программирования семейства .Net. Например, CodeDom предоставляет классы для создания переменных, классов, массивов, циклов и других составляющих объектно-ориентированного кода. В CodeDom нельзя генерировать любую текстовую информацию, за исключением прямой вставки текста применением класса CodeSnippetExpression, да и то в ограниченном контексте.

Применение CodeDom

Технология CodeDom позволяет генерировать код на нескольких языках. Для генерации кода необходимо создать дерево объектов из пространства имен System.Codedom, предоставляющее классы для отражения в языково-независимом стиле большинства программных конструкций. Эти объекты представляют собой синтаксические конструкции языков программирования, вроде объявления классов и переменных, циклов, условий, присваиваний, вызовов методов и т.д. Утверждается, что его применение позволяет не отвлекаться на нюансы разработки каждого языка программирования и быть уверенным, что CodeDom обработает все правильно. Утверждается также, что такая абстракция позволяет лучше структурировать код, позволяя при необходимости внесения изменений не беспокоиться об уже созданном коде. Однако далее мы увидим, что на практике вышеизложенное не всегда верно.

Самым верхним, или корневым объектом дерева является CodeCompileUnit. Он является абстрактным контейнером для хранения всех остальных объектов генерируемого кода.

//Создание объекта верхнего уровня дерева CodeDom
CodeCompileUnit compileUnit = new CodeCompileUnit();
    
Пример 6.1.

После создания дерева объектов используется объект CodeProvider, имеющийся у каждого языка среды .Net. В пространстве имен Microsoft.CSharp имеется класс CsharpCodeProvider, который содержит метод GenerateCodeFromCompileUnit, генерирующий код из объекта CodeCompileUnit. Для языка Visual Basic используется соответственно метод GenerateCodeFromCompileUnit класса VBCodeProvider из пространства имен Microsoft.VisualBasic.

Кроме метода GenerateCodeFromCompileUnit имеются методы для генерации кода из пространства имен (GenerateCodeFromNamespace), класса (GenerateCodeFromMember), выражения (GenerateCodeFromExpression), оператора (GenerateCodeFromStatement) и других конструкций. Пространство имен System.CodeDom.Complier определяет интерфейс ICodeGenerator, который может быть использован для вывода сгенерированного кода в объект TextWriter.

В примерах этой лекции мы продемонстрируем, как добавлять директивы импорта, объявлять классы, методы, переменные, создавать циклы, массивы. Все эти примеры в совокупности являются одним большим примером, код которого и будет приведен после объяснения всех особенностей применения CodeDom. Также будет приведен код сгенерированного кода на языках C# и Visual Basic.

Инициализация пространства имен

Для инициализации пространства имен в сгенерированном коде мы можем использовать следующий код:

//Инициализация объекта пространства имен с заданием имени
CodeNamespace MyGenericNameSpace = new CodeNamespace("MyNameSpace");
compileUnit.Namespaces.Add(MyGenericNameSpace);

//Импортирование пространств имен
MyGenericNameSpace.Imports.Add(new CodeNamespaceImport("System"));
MyGenericNameSpace.Imports.Add(new CodeNamespaceImport("System.Text"));
MyGenericNameSpace.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
    
Пример 6.2.

Этот код создает в генерируемом коде новое пространство имен, и импортирует в него пространства имен System, System.Text, а также System.Collections.Generic. Класс CodeNamespace используется для представления пространства имен, которое следует создать, в данном примере создается объект с именем MyNameSpace. Класс CodeNamespaceImport используется для представления пространства имен, которое следует импортировать. В нашем случае создается одно и импортируются три пространства имен.

Создание класса

Следующим этапом является построение класса. Для представления класса используется объект типа CodeTypeDeclaration. Класс CodeTypeDeclaration также позволяет создавать перечисления, структуры, интерфейсы. Однако по умолчанию создается класс с атрибутом public. Пример создания класса:

//Объявление класса
CodeTypeDeclaration MyGenericClass = new CodeTypeDeclaration("MyClass");
MyGenericNameSpace.Types.Add(MyGenericClass);
    
Пример 6.3.

В первой строчке генерируется класс с именем MyClass. Этот класс впоследствии может расширяться методами, свойствами, событиями и т.д. Во второй строчке добавляем его объявление в пространство имен MyGenericNameSpace.

Иерархия классов CodeDom

Так как не всегда бывает просто разобраться в том, как классы CodeDom взаимодействуют друг с другом, привожу схему с их иерархией. Эту схему можно использовать в процессе разбора примеров данной лекции, чтобы представлять себе общую картину. Также она будет полезна в процессе создания генератора.

Иерархия классов CodeDom

Рис. 6.1. Иерархия классов CodeDom

Создание метода

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

//Создание точки входа
CodeEntryPointMethod MyGenericMethod = new CodeEntryPointMethod();
MyGenericClass.Members.Add(MyGenericMethod);
    
Пример 6.4.

CodeEntryPointMethod представляет собой точку входа в программу - того места, с которого начинается выполнение программы. Для языка C# это будет метод Main. По умолчанию создается метод с атрибутами public и static. Для создания же обычного метода следует использовать класс CodeMemberMethod и также добавлять объект метода в свойство Members класса. Обязательным параметром будет имя нового метода.

Создание переменных, массивов и списков

Теперь займемся созданием непосредственно выполняемых команд, а начнем с инициализации переменных:

//Объявление типа List<string>
CodeTypeReference MyGenericListType = new CodeTypeReference(typeof(List<string>));
//Создание списка типа List<string> с именем MyList
CodeVariableDeclarationStatement MyGenericListVariable = 
    new CodeVariableDeclarationStatement(MyGenericListType,"MyList");
//Создание объекта позволяющего вызывать конструкторы
CodeObjectCreateExpression MyObjectCreateExpression =
    new CodeObjectCreateExpression();
//Установка вызова конструктора
MyObjectCreateExpression.CreateType = MyGenericListType;
MyGenericListVariable.InitExpression = MyObjectCreateExpression;
MyGenericMethod.Statements.Add(MyGenericListVariable);
    
Пример 6.5.

CodeTypeReference представляет собой тип данных в CodeDom. Его можно объявить заранее, чтобы впоследствии не делать этого при каждом объявлении переменной. В данном случае мы объявляем тип List<string>. Каждый язык из среды .Net имеет свои наименования типов данных из типов .Net. Например, имеется тип System.Int32 из среды .Net. Этот тип в C# и Visual Basic может называться по-разному. В CodeDom используется общее имя типа из среды .Net.

CodeVariableDeclarationStatement позволяет объявить переменную. В параметры передаются тип List<string> и имя MyList.CodeObjectCreateExpression представляющее выражение для создания нового объекта. В свойстве CreateType мы указали, что нужно создать новый объект с типом List<string>. А в качестве выражения инициализации переменной в свойстве InitExpression указали заранее созданное выражение.

Написанный выше пример создает такой код:

System.Collections.Generic.List<string> MyList = new System.Collections.Generic.List<string>();
    

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

//Создание переменной temp с типом string
CodeVariableDeclarationStatement MyTempVariable =
    new CodeVariableDeclarationStatement(typeof(System.String), "temp");
MyTempVariable.InitExpression = new CodeMethodInvokeExpression(
    new CodeTypeReferenceExpression("System.Console"),
    "ReadLine", new CodeArgumentReferenceExpression());
MyGenericMethod.Statements.Add(MyTempVariable);
    
Пример 6.6.

В данном примере объявляется строковая переменная temp, которой впоследствии присваивается значение, считанное с консольной строки. Класс CodeMethodInvokeExpression, как следует из названия, служит для создания выражения, вызывающего метод какого-нибудь класса. CodeTypeReferenceExpression указывает тип или класс, метод которого нужно вызвать. Далее мы передали название метода "ReadLine", который хотим вызвать. CodeArgumentReferenceExpression представляет собой ссылку на параметры метода.

Таким образом, генерируется следующий код:

string temp = System.Console.ReadLine();
    

Допустим, что надо создать подобный код:

int MyCount = System.Convert.ToInt32(temp);
    

Это можно сделать следующим образом:

//Создание переменной MyCount с типом int
CodeVariableDeclarationStatement MyCountVariable =
    new CodeVariableDeclarationStatement(typeof(System.Int32), "MyCount");
MyCountVariable.InitExpression = new CodeMethodInvokeExpression(
    new CodeTypeReferenceExpression("System.Convert"),
    "ToInt32", new CodeArgumentReferenceExpression("temp"));
MyGenericMethod.Statements.Add(MyCountVariable);
    
Пример 6.7.
< Лекция 6 || Лекция 7: 123 || Лекция 8 >
Дмитрий Клочков
Дмитрий Клочков
Россия, Рубцовск
Волков Олег
Волков Олег
Украина, Днепропетровск