Вы уже видели, как отражать типы в период выполнения, осуществлять позднее связывание с кодом и динамическое исполнение кода. Сделаем следующий шаг в логической последовательности — рассмотрим создание кода «на лету». При создании типов в период выполнения используется пространство имен
System.Reflection.Emit.
С помощью классов из этого пространства имен можно определять сборку в памяти, создавать для нее модули, определять для модулей новые типы (и их члены) и даже генерировать коды операций MSIL для реализации прикладной логики.
Несмотря на чрезвычайную простоту кода этого, примера, я отделил серверный код — DLL, содержащую класс, который создает метод
Hello-World, —
от клиентского кода, приложения, которое создает экземпляр класса, генерирующего код, и вызывает его метод
Hello World
(обратите внимание на переключатели компилятора в комментариях). Объяснение этого кода DLL приводится ниже:
using System;
using System.Reflection;
using System.Reflection.Emit;
namespace ILGenServer {
public class CodeGenerator {
public CodeGenerator() {
// Получить текущий currentDomain. currentDomain = AppDomain.CurrentDomain;
// Создать сборку в текущем currentDomain. assemblyName = new AssemblyNameQ; assemblyName.Name = "TempAssembly";
assemblyBuilder =
currentDomain.DefineDynamicAssembly
(assemblyName, AssemblyBuilderAccess.Run);
// Создать в этой сборке модуль moduleBuilder = assemblyBuilder.DefineOynamicModule ("TempModule");
// Создать тип в этой сборке typeBuilder = moduleBuilder.DefineType ("TempClass", TypeAttributes.Public); // Добавить к типу член (метод) methodBuilder = typeBuilder.DefineMethod ("HelloWorld", MethodAttributes.Public, null,null);
// Генерировать MSIL.
msil = methodBuilder.GetlLGeneratorO;
rasil.EmitWriteLine("Hello World
11
);
msil.Emit(OpCodes.Ret);
// Последний шаг: создание типа, t = typeBuilder.CreateTypeO;
>
AppDomain currentDomain; AssemblyName assemblyName; AssemblyBuilder assemblyBuilder; ModuleBuilder moduleBuilder; TypeBuilder typeBuilder; MethodBuilder roethodBuilder; ILGenerator msil; object o;
Type t; public Type T {
get {
return this.t; } > } }
Сначала мы создали экземпляр объекта
AppDomain
из текущей области (в главе 17 вы увидите, что прикладные области в функциональном плане похожи на процессы Win32). После этого мы создали экземпляр
объекта Assembly Name.
Класс
AssemblyName
подробно описан в главе 18, а вообще этот класс используется диспетчером кэша сборки для получения информации о ней. Получив текущую прикладную область и инициализированное имя сборки, вызываем метод
AppDomain.Defme-DynamicAssembly,
чтобы создать новую сборку. Заметьте, что два передаваемые нами аргумента являются именем сборки и описанием режима доступа к ней.
Assembly Builder Access.Run
указывает, что сборка может быть исполнена из памяти, но не может быть сохранена. Метод
AppDomain. Define DynamicAssembly
возвращает объект
Assembly Builder,
который мы затем приводим к объекту
Assembly.
На этом этапе у нас есть полнофункциональная сборка в памяти. Теперь нам нужно создать ее временный модуль и его тип.
Начнем с вызова метода
Assembly. DefineDynamicModule
для получения объекта
ModuleBuilder.
Получив этот объект, мы вызываем его метод
DefineType,
чтобы создать объект
ТуреВтШег,
передавая методу имя типа («
TempClass»)
и используемые для его определения атрибуты (
TypeAttri-butes.Public).
Теперь, имея объект
TypeBuilder,
можно создать член любого нужного нам типа. В данном случае мы создаем новый метод с помощью метода
TypeBuilder. DefineMethod.
В результате получаем совершенно новый тип
TempClass
с встроенным методом
HelloWorld.
Теперь все, что нам осталось сделать, — это решить, какой код поместить в этот метод. Для этого код создает экземпляр объекта
ILGenerator с
помощью метода
MethodBuilder.GetlLGenerator
и вызывает различные методы
IWenerator
для записи в метод MSIL-кода.
Здесь мы можем использовать стандартный код типа
Console. WriteLine
с помощью различных методов
IWenerator
или генерировать коды операций MSIL, используя метод
IWenerator. Emit.
Метод
IWenerator.Emit
в качестве единственного аргумента принимает поле члена класса
OpCodes,
непосредственно связанное с кодом операции MSIL.
В завершение вызываем метод
TypeBuilder.CreateType.
Это действие всегда должно выполняться последним, после того как вы определили члены нового типа. Далее мы получаем объект
Type
для нового типа с помощью метода
Type.GetType.
Этот объект будет храниться в члене-переменной клиентского приложения, которому он впоследствии понадобится.
Теперь все, что осталось сделать клиенту, — это получить член
Type
класса
CodeGenerator,
создать экземпляр активатора, экземпляр объекта
Methodlnfo
из типа и затем вызвать метод. Вот код, выполняющий эти действия, к которому добавлена небольшая проверка на наличие ошибок, чтобы быть уверенным, что все работает как надо:
using System;
using System.Reflection;
using ILGenServer;
public class ILGenClientApp {
public static void Main() {
Console.WriteLine("Calling DLL function to generate " + "a new type and method in memory..."); CodeGenerator gen = new CodeGeneratorQ;
Console.WriteLine("Retrieving dynamically generated
type..."); Type t = gen.T; if (null != t) {
Console.WriteLine("Instantiating the new type...");
object о = Activator.Createlnstance(t);
Console.WriteLine("Retrieving the type's " +
"HelloWorld method...");
Methodlnfo helloWorld = t.GetMethod("HelloWorld"); if (null != helloWorld) {
Console.WriteLine("Invoking our dynamically " +
"created HelloWorld method..."); helloWorld.Invoke(o, null); }
else <
Console.WriteLine("Could not locate " + "HelloWorld method"); } >
else {
Console.WriteLin&("Could not access Type from server"); > > >
Скомпоновав и исполнив это приложение, вы увидите:
Calling DLL function to generate a new type and method in memory...
Retrieving dynamically generated type...
Instantiating the new type...
Retrieving the type's HelloWorld method...
Invoking our dynamically created HelloWorld method...
Hello World