В данной лекции мы рассмотрим еще одну технологию Microsoft для генерации кода - CodeDom (Code Document Object Model). На русский язык это название переводится как объектная модель документов кода. Технология CodeDom позволяет строить модель кода, используя набор объектов. Впоследствии провайдер кода может конвертировать эту модель в программный код на конкретном языке, вроде C# или Visual Basic или другой язык из семейства языков .Net. Применение CodeDom отличается от применения шаблонов или программ, напрямую манипулирующих текстом для генерации кода. Пространство имен CodeDom является набором классов, представляющих понятия объектно-ориентированных языков программирования семейства .Net. Например, CodeDom предоставляет классы для создания
Технология CodeDom позволяет генерировать код на нескольких языках. Для генерации кода необходимо создать дерево объектов из пространства имен System.Codedom, предоставляющее классы для отражения в языково-независимом стиле большинства программных конструкций. Эти объекты представляют собой синтаксические конструкции языков программирования, вроде объявления классов и переменных, циклов, условий, присваиваний, вызовов методов и т.д. Утверждается, что его применение позволяет не отвлекаться на нюансы разработки каждого языка программирования и быть уверенным, что CodeDom обработает все правильно. Утверждается также, что такая абстракция позволяет лучше структурировать код, позволяя при необходимости внесения изменений не беспокоиться об уже созданном коде. Однако далее мы увидим, что на практике вышеизложенное не всегда верно.
Самым верхним, или корневым объектом дерева является CodeCompileUnit. Он является абстрактным контейнером для хранения всех остальных объектов генерируемого кода.
//Создание объекта верхнего уровня дерева CodeDom
CodeCompileUnit compileUnit = new CodeCompileUnit();
После создания дерева объектов используется объект 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"));
Этот код создает в генерируемом коде новое пространство имен, и импортирует в него пространства имен System, System.Text, а также System.Collections.Generic. Класс CodeNamespace используется для представления пространства имен, которое следует создать, в данном примере создается объект с именем MyNameSpace. Класс CodeNamespaceImport используется для представления пространства имен, которое следует импортировать. В нашем случае создается одно и импортируются три пространства имен.
Следующим этапом является построение класса. Для представления класса используется объект типа CodeTypeDeclaration. Класс CodeTypeDeclaration также позволяет создавать перечисления, структуры, интерфейсы. Однако по умолчанию создается класс с атрибутом public. Пример создания класса:
//Объявление класса
CodeTypeDeclaration MyGenericClass = new CodeTypeDeclaration("MyClass");
MyGenericNameSpace.Types.Add(MyGenericClass);
В первой строчке генерируется класс с именем MyClass. Этот класс впоследствии может расширяться методами, свойствами, событиями и т.д. Во второй строчке добавляем его объявление в пространство имен MyGenericNameSpace.
Так как не всегда бывает просто разобраться в том, как классы CodeDom взаимодействуют друг с другом, привожу схему с их иерархией. Эту схему можно использовать в процессе разбора примеров данной лекции, чтобы представлять себе общую картину. Также она будет полезна в процессе создания генератора.
(рис 6.1) Иерархия классов CodeDom
Вновь вернемся к рассмотрению примеров. После создания класса займемся добавлением метода к этому классу, а именно создадим точку
//Создание точки входа
CodeEntryPointMethod MyGenericMethod = new CodeEntryPointMethod();
MyGenericClass.Members.Add(MyGenericMethod);
CodeEntryPointMethod представляет собой точку 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);
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);
В данном примере объявляется строковая переменная 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);
Рассмотрим код, создающий цикл:
//Создание цикла
CodeVariableDeclarationStatement MyVariable = new CodeVariableDeclarationStatement(typeof(int), "i");
MyVariable.InitExpression = new CodeSnippetExpression("0");
CodeIterationStatement forLoop = new CodeIterationStatement();
forLoop.InitStatement = MyVariable;
CodeAssignStatement assignment = new CodeAssignStatement(
new CodeVariableReferenceExpression("i"),
new CodeSnippetExpression("i + 1"));
forLoop.IncrementStatement = assignment;
forLoop.TestExpression = new CodeSnippetExpression("i < MyCount");
MyGenericMethod.Statements.Add(forLoop);
CodeIterationStatement используется для создания цикла for. CodeSnippetExpression подставляет блок текста в генерируемый код. Через свойство цикла InitStatement назначается условие IncrementStatement применяется для задания условия увеличения счетчика переменной цикла. CodeAssignStatement назначает переменной новое значение. При этом CodeVariableReferenceExpression выводит имя переменной в генерируемый код. Через свойство цикла TestExpression назначается условие, при котором выполняется цикл.
Если мы сгенерируем код в таком виде, то содержимое цикла у нас будет пустым. Добавим несколько операторов в свойство Statements цикла:
CodeBinaryOperatorExpression operate = new CodeBinaryOperatorExpression(
new CodePrimitiveExpression("item "),
CodeBinaryOperatorType.Add,
new CodeVariableReferenceExpression("i"));
forLoop.Statements.Add(new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeVariableReferenceExpression("MyList"),
"Add"),operate));
Внутри цикла через CodeMethodReferenceExpression создается вызов метода Add. При этом в параметры передается выражение CodeBinaryOperatorExpression, представляющее собой CodeBinaryOperatorType указываем, что нам нужно сложение, то есть Add. В первой части выражения будет стоять строка "item ", а во второй части - переменная i.
Аналогично добавим вывод на консоль значения элемента списка:
CodeExpression expr = new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeVariableReferenceExpression("System.Console"),
"WriteLine"),
new CodeArgumentReferenceExpression("MyList[i]"));
forLoop.Statements.Add(expr);
CodeArgumentReferenceExpression служит для ссылки на выражение аргумента функции. В завершение в свойство Statements нашего метода добавим еще код для считывания клавиши:
CodeExpression readkey = new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeVariableReferenceExpression("System.Console"),
"ReadKey"),
new CodeArgumentReferenceExpression(""));
MyGenericMethod.Statements.Add(readkey);
Мы рассмотрели возможности построения дерева CodeDom, однако нужно еще сгенерировать код и сохранить его. Для этого используется метод GenerateCodeFromCompileUnit провайдера:
private static void WriteGeneratedFile(CodeCompileUnit compileUnit, string lang)
{
string path = @"C:\Users\agpx\Desktop\Code Generation\Code Generation Lections\examples CodeDom\MyGenericExample";
CodeDomProvider provider;
if (lang == "C#")
{
provider = new CSharpCodeProvider();
path += ".cs";
}
else
{
provider = new VBCodeProvider();
path += ".vb";
}
using (StreamWriter sw = new StreamWriter(path, false))
{
IndentedTextWriter tw = new IndentedTextWriter(sw, " ");
provider.GenerateCodeFromCompileUnit(compileUnit, tw, new CodeGeneratorOptions());
tw.Close();
}
}
А теперь посмотрим на созданную нами программу, объединим все предыдущие примеры и получим следующий результат:
//Создание дерева CodeDom
CodeCompileUnit compileUnit = new CodeCompileUnit();
//Инициализация объекта пространства имен с заданием имени
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"));
//Объявление класса
CodeTypeDeclaration MyGenericClass = new CodeTypeDeclaration("MyClass");
//MyGenericClass.Attributes = MemberAttributes.Static;
//MyGenericClass.TypeAttributes = System.Reflection.TypeAttributes.NotPublic;
//MyGenericClass.IsClass = true;
MyGenericNameSpace.Types.Add(MyGenericClass);
//Создание точки входа
CodeEntryPointMethod MyGenericMethod = new CodeEntryPointMethod();
MyGenericClass.Members.Add(MyGenericMethod);
//Объявление типа 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);
//Создание переменной 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);
//Создание переменной 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);
//Создание цикла
CodeVariableDeclarationStatement MyVariable = new CodeVariableDeclarationStatement(typeof(int), "i");
CodeIterationStatement forLoop = new CodeIterationStatement();
MyVariable.InitExpression = new CodeSnippetExpression("0");
forLoop.InitStatement = MyVariable;
CodeAssignStatement assignment = new CodeAssignStatement(
new CodeVariableReferenceExpression("i"),
new CodeSnippetExpression("i + 1"));
forLoop.IncrementStatement = assignment;
forLoop.TestExpression = new CodeSnippetExpression("i < MyCount");
MyGenericMethod.Statements.Add(forLoop);
CodeBinaryOperatorExpression operate = new CodeBinaryOperatorExpression(
new CodePrimitiveExpression("item "),
CodeBinaryOperatorType.Add,
new CodeVariableReferenceExpression("i.ToString()"));
forLoop.Statements.Add(new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeVariableReferenceExpression("MyList"),
"Add"),operate));
CodeExpression expr = new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeVariableReferenceExpression("System.Console"),
"WriteLine"),
new CodeArrayIndexerExpression(
new CodeVariableReferenceExpression("MyList"),
new CodeVariableReferenceExpression("i")
));
forLoop.Statements.Add(expr);
CodeExpression readkey = new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeVariableReferenceExpression("System.Console"),
"ReadKey"),
new CodeArgumentReferenceExpression(""));
MyGenericMethod.Statements.Add(readkey);
WriteGeneratedFile(compileUnit, "C#");
WriteGeneratedFile(compileUnit, "VB");
На языке C# будет сгенерирован следующий код (без автоматически добавленных при генерации комментариев):
namespace MyNameSpace
{
using System;
using System.Text;
using System.Collections.Generic;
public class MyClass
{
public static void Main()
{
System.Collections.Generic.List<string> MyList = new System.Collections.Generic.List<string>();
string temp = System.Console.ReadLine();
int MyCount = System.Convert.ToInt32(temp);
for (int i = 0; i < MyCount; i = i + 1)
{
MyList.Add(("item " + i.ToString()));
System.Console.WriteLine(MyList[i]);
}
System.Console.ReadKey();
}
}
}
Как видим, чтобы сгенерировать одну строчку кода, нужно написать от 5 и более строчек кода генератора. Однако такая, казалось бы неэффективная, затрата труда может компенсироваться тем, что генератор можно применить для создания гораздо большего объема кода, чем код самого генератора.
На Visual Basic код будет выглядеть так (без автоматически добавленных при генерации комментариев):
Option Strict Off
Option Explicit On
Imports System
Imports System.Collections.Generic
Imports System.Text
Namespace MyNameSpace
Public Class [MyClass]
Public Shared Sub Main()
Dim MyList As System.Collections.Generic.List(Of String) = New System.Collections.Generic.List(Of String)
Dim temp As String = System.Console.ReadLine()
Dim MyCount As Integer = System.Convert.ToInt32(temp)
Dim i As Integer = 0
Do While i < MyCount
MyList.Add(("item " + i.ToString()))
System.Console.WriteLine(MyList(i))
i = i + 1
Loop
System.Console.ReadKey()
End Sub
End Class
End Namespace
Хоть сформированный код на Visual Basic не содержит синтаксических ошибок, не спешите внедрять его - он не будет работать корректно. Это объясняется тем, что правила создания форм на Visual Basic и C# отличаются. И даже когда сгенерированный код на одном языке работает прекрасно, если его создать на другом, то этот код может работать не так, как ожидалось, либо вообще не работать. Это открывает нам следующий недостаток CodeDom - если необходимо выполнять генерацию для нескольких языков, нужно очень тщательно учитывать различия между ними и стараться применять такие программные конструкции, которые являются общими для этих языков.
Также эту технологию можно использовать для динамической компиляции кода. Компилировать можно как из файла кода, так и из CompileUnit.
string path = @"C:\Users\agpx\Desktop\Code Generation\Code Generation Lections\examples CodeDom\MyGenericExample";
string outputexe = path + ".exe";
CodeDomProvider provider;
if (lang == "C#")
{
provider = new CSharpCodeProvider();
path += ".cs";
}
else
{
provider = new VBCodeProvider();
path += ".vb";
}
//Установка параметров для выполнения компиляции
CompilerParameters cp = new CompilerParameters();
//Добавление ссылки на сборку
cp.ReferencedAssemblies.AddRange( new string[] {"System.dll", "System.Data.dll", "System.Drawing.dll",
"System.Windows.Forms.dll", "System.XML.dll"});
//Уставка необходимости генерации исполняемого файла, а не библиотеки
cp.GenerateExecutable = true;
//Установка имени и пути файла, который будет генерироваться
cp.OutputAssembly = outputexe;
//Сохранить сборку как физический файл, а не в памяти
cp.GenerateInMemory = false;
//Запустить компиляцию
CompilerResults cr = provider.CompileAssemblyFromDom(cp, compileUnit);
Каковы же преимущества применения CodeDom? Они следующие:
Имеются следующие недостатки применения:
unsafe в C#. Хотя эти ограничения можно обойти применением класса CodeSnippetExpression, но такой прием сильно уменьшает гибкость генерации для разных языков.Если есть необходимость генерировать один и тот же код сразу на нескольких языках, то CodeDom может оказаться весьма полезным инструментом. Эта технология активно используется самой компанией Microsoft. Особенно часто она применяется в Visual Studio, где при выполнении каких-либо действий в зависимости от выбранного языка автоматически создаются объекты и программный код. Теоретически, есть возможность генерировать код для любого языка, для которого существует провайдер CodeDom. Провайдер также включает поддержку компиляции сгенерированного кода, при этом набор объектов CodeDom конвертируется напрямую в MSIL.
CodeDom поддерживает любой язык, для которого есть провайдер. Разработчики также могут добавить новый язык, создав для него свой провайдер.
Однако, если требуется генерировать код только для одном языке, то применение этой технологии может не оправдать себя. Гораздо проще может оказаться применение шаблонов либо манипуляция строками текста.
CodeDom абстрактен настолько, что позволяет выполнять генерацию для нескольких языков. Однако и сложен настолько, что если не нужно генерировать код одновременно на нескольких языках, то его применение может быть невыгодным, затраты времени могут оказаться слишком большими.
При применении CodesDom следует:
CodeSnippetExpression. Это снижает гибкость кода при генерации на нескольких языках и может увеличить число ошибок;Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.