Процесс создания сложных .NET-приложений на CIL будет довольно “неблагодарным трудом”. С одной стороны, CIL представляет собой чрезвычайно выразительный язык программирования, позволяющий взаимодействовать со всеми программными конструкциями, которые предусмотрены в CTS. С другой стороны, написание кода непосредственно на CIL отнимает много времени и сил и чревато допущением ошибок. И хотя знание — это всегда сила, все же наверняка интересно, насколько в действительности важно держать правила синтаксиса CIL в голове? Ответ на этот вопрос зависит от ситуации. Конечно, в большинстве случаев при программировании .NET-приложений просматривать, редактировать или создавать CIL-код совсем необязательно. Однако знание основ CIL позволяет перейти к исследованию мира динамических сборок (в отличие от статических) и оценки роли пространства имен System.Reflection.Emit.
В первую очередь может возникнуть вопрос, чем отличаются статические и динамические сборки? По определению, статической сборкой называется такой двоичный модуль .NET, который загружается прямо из хранилища на диске, т.е. на момент запроса CLR-средой он находится в физическом файле (или файлах, если сборка многофайловая) где-то на жестком диске. Как не трудно догадаться, при каждой компиляции исходного кода C# в результате всегда получается статическая сборка.
С другой стороны, динамическая сборка создается в памяти “на лету” за счет использования типов из пространства имен System.Reflection.Emit. Пространство имен System.Reflection.Emit, по сути, позволяет сделать так, чтобы сборка с ее модулями, определениями типов и логикой реализации на CIL создавалась во время выполнения. После этого сборку в памяти можно сохранять в дисковый файл, превращая ее в статическую. Несомненно, для создания динамических сборок с помощью пространства имен System.Reflection.Emit нужно разбираться в природе кодов операций CIL.
Хотя процесс создания динамических сборок является довольно сложным (и нечасто применяемым) приемом программирования, он полезен в перечисленных ниже ситуациях:
- Создается инструмент для программирования .NET, который должен быть способен генерировать сборки по требованию на основе вводимых пользователем данных.
- Создается приложение, которое должно уметь генерировать прокси для удаленных типов на лету на основе получаемых метаданных.
- Требуется возможность загрузки статической сборки и динамической вставки в ее двоичный образ новых типов.
Некоторые компоненты механизма исполняющей среды .NET тоже предусматривают генерацию динамических сборок в фоновом режиме. Например, в ASP.NET эта технология применяется для отображения кода разметки и серверного сценария на объектную модель исполняющей среды. В LINQ код тоже может генерироваться “на лету” на основе различных выражений, содержащихся в запросах. Давайте посмотрим, какие типы предлагаются в пространстве имен System.Reflection.Emit.
Пространство имен System.Reflection.Emit
Для создания динамических сборок необходимо иметь представление о кодах операций CIL, однако типы, поставляемые в пространстве имен System.Reflection.Emit, максимально возможно скрывают сложные детали CIL. Например, вместо того, чтобы напрямую указывать необходимые директивы и атрибуты CIL для определения типа класса, можно воспользоваться классом TypeBuilder. Еще один класс, ConstructorBuilder, позволит определить новый конструктор на уровне экземпляра, не имея дела напрямую с лексемами specialname, rtspecialname или .ctor. Ключевые члены пространства имен System.Reflection.Emit перечислены ниже:
| Член класса | Описание |
| AssemblyBuilder | Используется для создания сборки (*.dll или *.ехе) во время выполнения. В сборках *.ехе должен обязательно вызываться метод ModuleBuilder.SetEntryPoint() для указания метода, который должен выступать в роли точки входа в модуль. Если точка входа не задана, генерируется файл *.dll. |
| ModuleBuilder | Используется для определения набора модулей внутри текущей сборки |
| EnumBuilder | Используется для создания типа перечисления .NET |
| TypeBuilder | Может применяться для создания в модуле различных классов, интерфейсов, структур и делегатов во время выполнения |
| MethodBuilder LocalBuilder PropertyBuilder FieldBuilder ConstructorBuilder CustomAttributeBuilder ParameterBuilder EventBuilder |
Используются для создания во время выполнения соответствующих членов типов (таких как методы, локальные переменные, свойства, конструкторы и атрибуты) |
| ILGenerator | Генерирует необходимые коды операций CIL внутри указанного члена типа |
| Opcodes | Предоставляет множество полей, которые отображаются на коды операций CIL. Используется вместе с различными членами System.Reflection.Emit.ILGenerator |
В целом типы из пространства имен System.Reflection.Emit позволяют представлять исходные лексемы CIL программным образом во время построения динамической сборки.
Тип System.Reflection.Emit.ILGenerator
Роль типа ILGenerator заключается во вставке соответствующих кодов операций CIL в заданный член типа. Напрямую создавать объекты ILGenerator нельзя, потому что этот тип не имеет никаких общедоступных конструкторов. Вместо этого объекты ILGenerator должны получаться за счет вызова конкретных методов Builder-типов (таких как MethodBuilder и ConstructorBuilder), например:
// Получение ILGenerator из объекта ConstructorBuilder по имени myCtorBuilder ConstructorBuilder myCtorBuilder = new ConstructorBuilder (/* ...различные аргументы... */) ; ILGenerator myCILGen = myCtorBuilder.GetILGenerator();
После получения ILGenerator можно приступать к генерации с его помощью низкоуровневых кодов операций CIL, применяя любые его методы, главным из которых является метод Emit(), который работает вместе с типом класса System.Reflection.Emit.Opcodes.