Динамические сборки
53C# --- Сборки .NET --- Динамические сборки
Процесс создания сложных .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.