<b>// Создать специальный конструктор, принимающий</b>
<b>// единственный аргумент типа string.</b>
Type[] constructorArgs = new Type[1];
constructorArgs[0] = typeof(string);
ConstructorBuilder constructor =
helloWorldClass.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
constructorArgs);
<b>// Выпустить необходимый код CIL для конструктора.</b>
ILGenerator constructorIl = constructor.GetILGenerator();
constructorIl.Emit(OpCodes.Ldarg_0);
Type objectClass = typeof(object);
ConstructorInfo superConstructor =
objectClass.GetConstructor(new Type[0]);
constructorIl.Emit(OpCodes.Call, superConstructor);
<b>// Загрузить в стек указатель this объекта.</b>
constructorIl.Emit(OpCodes.Ldarg_0);
constructorIl.Emit(OpCodes.Ldarg_1);
<b>// Загрузить входной аргумент в виртуальный стек и сохранить его в msgField</b>
constructorIl.Emit(OpCodes.Stfld, msgField);
constructorIl.Emit(OpCodes.Ret);
Как вам теперь уже известно, в результате определения специального конструктора для типа стандартный конструктор молча удаляется. Чтобы снова определить конструктор без аргументов, нужно просто вызвать метод
DefineDefaultConstructor()
типа
TypeBuilder
:
<b>// Создать стандартный конструктор.</b>
helloWorldClass.DefineDefaultConstructor(
MethodAttributes.Public);
Выпуск метода SayHello()
В заключение давайте исследуем процесс выпуска метода
SayHello()
. Первая задача связана с получением объекта типа
MethodBuilder
из переменной
helloWorldClass
. После этого можно определить сам метод и получить внутренний объект типа
ILGenerator
для вставки необходимых инструкций CIL:
<b>// Создать метод SayHello.</b>
MethodBuilder sayHiMethod = helloWorldClass.DefineMethod(
"SayHello", MethodAttributes.Public, null, null);
methodIl = sayHiMethod.GetILGenerator();
<b>// Вывести строку на консоль.</b>
methodIl.EmitWriteLine("Hello from the HelloWorld class!");
methodIl.Emit(OpCodes.Ret);
Здесь был определен открытый метод (т.к. указано значение
MethodAttributes.Public
), который не имеет параметров и ничего не возвращает (на что указывают значения
null
в вызове
DefineMethod()
). Также обратите внимание на вызов
EmitWriteLine()
. Посредством данного вспомогательного метода класса
ILGenerator
можно записать строку в стандартный поток вывода, приложив минимальные усилия.
Использование динамически сгенерированной сборки
Теперь, когда у вас есть логика для создания сборки, осталось лишь выполнить сгенерированный код. Логика в вызывающем коде обращается к методу
CreateMyAsm()
, получая ссылку на созданный объект
AssemblyBuilder.
Далее вы поупражняетесь с поздним связыванием (см. главу 17) для создания экземпляра класса
HelloWorld
и взаимодействия с его членами. Модифицируйте операторы верхнего уровня, как показано ниже:
using System;
using System.Reflection;
using System.Reflection.Emit;
Console.WriteLine("***** The Amazing Dynamic Assembly Builder App *****");
// Создать объект AssemblyBuilder с использованием вспомогательной функции.
AssemblyBuilder builder = CreateMyAsm();
// Получить тип HelloWorld.
Type hello = builder.GetType("MyAssembly.HelloWorld");
// Создать экземпляр HelloWorld и вызвать корректный конструктор.
Console.Write("-> Enter message to pass HelloWorld class: ");
string msg = Console.ReadLine();
object[] ctorArgs = new object[1];
ctorArgs[0] = msg;
object obj = Activator.CreateInstance(hello, ctorArgs);
// Вызвать метод SayHelloO и отобразить возвращенную строку.
Console.WriteLine("-> Calling SayHello() via late binding.");
MethodInfo mi = hello.GetMethod("SayHello");
mi.Invoke(obj, null);
// Вызвать метод GetMsg().
mi = hello.GetMethod("GetMsg");
Console.WriteLine(mi.Invoke(obj, null));
Фактически только что была построена сборка .NET Core, которая способна создавать и запускать другие сборки .NET Core во время выполнения. На этом исследование языка CIL и роли динамических сборок завершено. Настоящая глава должна была помочь углубить знания системы типов .NET Core, синтаксиса и семантики языка CIL, а также способа обработки кода компилятором C# в процессе его компиляции.