Динамические сборки

53

Процесс создания сложных .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 тоже предусматривают генерацию динамических сборок в фоновом режиме. Например, в 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.

Пройди тесты
Лучший чат для C# программистов