В общем, типы пространства имен System.Reflection.Emit при построении динамического двоичного модуля позволяют представлять "сырые" лексемы CIL программными единицами. Возможности использования многих из указанных членов будут продемонстрированы в следующем примере, но тип ILGenerator заслуживает отдельного обсуждения.
Роль System.Reflection.Emit.ILGenerator
Как следует из самого имени указанного типа, роль ILGenerator заключается в добавлении кодов операций CIL в данный член типа. Обычно нет необходимости непосредственно создавать объект ILGenerator, а нужно просто получить действительную ссылку на тип ILGenerator, используя типы, связанные с компоновщиком (такие как MethodBuilder и ConstructorBuilder). Например:
// Получение ILGenerator из объекта ConstructorBuilder
// с именем 'myCtorBuilder'.
ConstructorBuilder myCtorBuilder = new ConstructorBuilder (/*…различные аргументы… */);
ILGenerator myCILGen = myCtorBuilder.GetILGenerator();
Имея ILGenerator, вы можете генерировать "сырые" коды операций CIL, используя любые из целого набора методов. Некоторые (но, конечно же, не все) методы ILGenerator описаны в табл. 15.9.
Таблица 15.9. Подборка методов ILGenerator
| Метод | Описание |
|---|---|
| BeginCatchBlock() | Начинает блок catch |
| BeginExceptionBlock() | Начинает блок неотфильтрованного исключения |
| BeginFinallyBlock() | Начинает блок finally |
| BeginScope() | Начинает лексический контекст |
| DeclareLocal() | Объявляет локальную переменную |
| DefineLabel() | Объявляет новую метку |
| Emit() | Является перегруженным и позволяет генерировать коды операций CIL |
| EmitCall() | Вставляет код операции call или callvirt в поток CIL |
| EmitWriteLine() | Генерирует вызов Console.WriteLine() с различными типами значений |
| EndExceptionBlock() | Завершает блок исключения |
| EndScope() | Завершает лексический контекст |
| ThrowException() | Создает инструкцию для генерирования исключения |
| UsingNamespace() | Указывает пространство имен, которое будет использоваться для оценки локальных переменных и наблюдаемых значений в текущем активном лексическом контексте |
Ключевым методом ILGenerator является метод Emit(), который работает в совокупности с типом класса System.Reflection.Emit.OpCodes. Как уже упоминалось в этой главе, данный тип открывает большой набор доступных только для чтения полей, отображающихся в коды операций CIL. Полностью эти члены описаны в оперативно доступной системе справки, но целый ряд примеров вы сможете увидеть и на следующих страницах.
Генерирование динамического компоновочного блока
Чтобы проиллюстрировать процесс определения компоновочного блока .NET в среде выполнения, давайте создадим одномодульный динамический компоновочный блок с именем MyAssembly.dll. В этом модуле будет содержаться класс HelloWorld. Тип HelloWorld поддерживает конструктор, используемый по умолчанию, и пользовательский конструктор для присваивания значения приватной переменной (theMessage) типа string. Кроме того, HelloWorld предлагает открытый метод экземпляра с именем SayHello(), который выводит приветствие в стандартный поток ввода-вывода, а также еще один метод экземпляра, GetMsg(), который возвращает внутреннюю приватную строку. В результате вы должны программно сгенерировать следующий тип класса.
// Этот класс будет создан в среде выполнения
// с помощью System.Reflection.Emit.
public class HelloWorld {
private string theMessage;
HelloWorld() {}
HelloWorld(string s) { theMessage = s; }
public string GetMsg() { return theMessage; }
public void SayHello() {
System.Console.WriteLine("Привет от класса HelloWorld!");
}
}