| Рабочим названием платформы .NET было |
Верификация CIL-кода. Библиотеки для создания метаинструментов
Reflection API
Библиотека рефлексии содержит классы для работы с метаданными на высоком уровне (эти классы располагаются в пространствах имен System.Reflection и System.Reflection.Emit ). Она соответствует спецификации CLS, поэтому использующий ее метаинструмент может быть написан на любом языке платформы .NET, который является потребителем CLS-библиотек.
Чтение метаданных через рефлексию
Метаинструмент, использующий библиотеку рефлексии, получает доступ к метаданным сборки через экземпляр класса Assembly, который создается при загрузке сборки в память. Затем через методы класса Assembly можно получить объекты класса Module, которые соответствуют модулям, входящим в сборку. Класс Module, в свою очередь, позволяет получить набор экземпляров класса Type для входящих в модуль типов, а уже через эти объекты можно добраться до конструкторов (класс ConstructorInfo ), методов (класс MethodInfo ) и полей (класс FieldInfo ) типов. То есть библиотека рефлексии спроектирована таким образом, что создание объектов рефлексии, соответствующих элементам метаданных, осуществляется путем вызова методов других объектов рефлексии (схема получения доступа к объектам рефлексии показана на рис. 4.5, при этом сплошные стрелки обозначают основные пути получения доступа, а пунктирные - дополнительные пути). Другими словами, конструкторы классов, входящих в библиотеку, для пользователя библиотеки недоступны. Информацию о любом элементе метаданных можно прочитать из свойств соответствующего ему объекта рефлексии.
Экземпляр класса Assembly, с создания которого, как правило, начинается работа со сборкой через рефлексию, можно получить разными способами:
- Если нас интересует текущая работающая сборка, мы можем вызвать статический метод GetExecutingAssembly класса Assembly:
Assembly assembly = Assembly.GetExecutingAssembly()
- Если мы хотим получить доступ к сборке, в которой объявлен некоторый тип данных, мы можем воспользоваться статическим методом GetAssembly:
Assembly assembly = Assembly.GetAssembly(typeof(int))
- И, наконец, если нам надо загрузить внешнюю сборку, мы используем статический метод LoadFrom:
Assembly assembly = Assembly.LoadFrom("test.exe")
Сборка .NET, как правило, состоит из одного модуля, хотя существует возможность включения в сборку сразу нескольких модулей. Получение доступа к объектам класса Module, описывающим модули, из которых состоит сборка, осуществляется одним из двух способов:
- Если мы знаем имя модуля, то мы можем использовать метод GetModule класса Assembly:
Module module = assembly.GetModule("SomeModule.exe") - Кроме того, мы можем вызвать метод GetModules для получения массива объектов класса Module, соответствующих всем модулям в сборке:
Module[] modules = assembly.GetModules(false)
Через объект класса Module мы можем обратиться к глобальным полям и функциям модуля (глобальные поля и функции не принадлежат ни одному классу), используя методы GetField и GetFields для полей, а также GetMethod и GetMethods для функций. При этом полям будут соответствовать объекты класса FieldInfo, а методам - объекты класса MethodInfo. В следующем примере мы выводим на экран сигнатуры всех глобальных функций некоторой сборки "test.exe":
Assembly assembly = Assembly.LoadFrom("test.exe");
Module[] modules = assembly.GetModules(false);
foreach (Module mod in modules)
{
MethodInfo[] methods = mod.GetMethods();
foreach (MethodInfo met in methods)
Console.WriteLine(met);
}Особое место в библиотеке рефлексии занимает класс Type, представляющий типы. Область применения этого класса значительно шире, чем области применения остальных классов рефлексии, и этот факт отражен в том обстоятельстве, что класс Type входит в пространство имен System, а не System.Reflection.
В каждом классе существует унаследованный от класса System.Object метод GetType, возвращающий экземпляр класса Type, который описывает этот класс. В следующем примере метод GetType будет использован для динамического определения типа объекта o (так как этот объект представляет собой строку, то на печать будет выведено сообщение "Sytem.String"):
object o = new String("qwerty");
Type t = o.GetType();
Console.WriteLine(t);В C# определен специальный оператор typeof, который возвращает объект класса Type. Например, если нам нужно получить объект Type, представляющий тип массива строк, мы можем записать:
Type t = typeof(string[]);
Кроме этого, доступ к информации о типе можно получить, зная имя этого типа:
- путем вызова статического метода GetType класса Type:
Type t = Type.GetType("System.Char"); - через объекты классов Assembly или Module, которые соответствуют сборке или модулю, содержащему нужный тип:
- Type t = module.GetType("Class1") ;
- Type t = assembly.GetType("Class1").
Мы также можем воспользоваться методами GetTypes классов Assembly и Module для получения массива типов, объявленных в сборке или модуле. В следующем примере на экран выводится список типов, содержащихся в сборке "test.exe":
Assembly assembly = Assembly.LoadFrom("test.exe");
Type[] types = assembly.GetTypes();
foreach (Type t in types)
Console.WriteLine(t);Имея объект рефлексии, соответствующий некоторому типу, мы имеем возможность получить доступ к объектам рефлексии, описывающим его поля, конструкторы, методы, свойства и вложенные типы. При этом, если мы знаем их имена, мы можем воспользоваться методами GetField, GetConstructor, GetMethod, GetProperty и GetNestedType класса Type. А если мы хотим получить массивы объектов рефлексии, описывающих члены типа, то нам нужно вызвать методы GetFields, GetConstructors, GetMethods, GetProperties и GetNestedTypes. В следующем примере на экран выводится список типов, содержащихся в сборке, вместе с сигнатурами их методов:
Assembly assembly = Assembly.LoadFrom("test.exe");
Type[] types = assembly.GetTypes();
foreach (Type t in types)
{
Console.WriteLine(""+t+":");
MethodInfo[] methods = t.GetMethods();
foreach (MethodInfo m in methods)
Console.WriteLine(" "+m);
}Для каждого метода и конструктора мы можем получить доступ к массиву объектов рефлексии, описывающих его параметры. Для этого служит метод GetParameters, объявленный в классах MethodInfo и ConstructorInfo. Данный метод возвращает массив объектов класса ParameterInfo:
ParameterInfo[] parms = method.GetParameters();
Управление объектами
Кроме чтения метаданных, библиотека рефлексии позволяет создавать экземпляры типов, входящих в обрабатываемую сборку, вызывать методы этих типов, читать и изменять значения полей.
Рассмотрим класс TestClass:
class TestClass
{
private int val;
public TestClass() { val = 7; }
protected void print() { Console.WriteLine(val); }
}В следующем примере мы используем метод Invoke класса MethodInfo для вызова метода print объекта класса TestClass. Обратите внимание, что метод print объявлен с модификатором доступа protected.
static void CallMethodDemo()
{
TestClass a = new TestClass();
BindingFlags flags = (BindingFlags)
(BindingFlags.NonPublic | BindingFlags.Instance);
MethodInfo printer =
typeof(TestClass).GetMethod("print",flags);
printer.Invoke(a, null);
}Путем внесения небольших изменений в CallMethodDemo мы можем добиться того, что объект класса TestClass будет также создаваться через рефлексию (с помощью вызова его конструктора):
static void CallMethodDemo2()
{
Type t = typeof(TestClass);
BindingFlags flags = (BindingFlags)
(BindingFlags.NonPublic | BindingFlags.Instance);
ConstructorInfo ctor = t.GetConstructor(new Type[0]);
MethodInfo printer = t.GetMethod("print",flags);
object a = ctor.Invoke(null);
printer.Invoke(a, null);
}А теперь продемонстрируем, как через рефлексию можно получить значение поля объекта. Это достигается путем вызова метода GetField объекта класса Type:
static void GetFieldDemo()
{
TestClass a = new TestClass();
BindingFlags flags = (BindingFlags)
(BindingFlags.NonPublic |
BindingFlags.Instance);
FieldInfo val =
typeof(TestClass).GetField("val",flags);
Console.WriteLine(val.GetValue(a));
}Генерация метаданных и CIL-кода
Для генерации метаданных в библиотеке рефлексии предназначены классы пространства имен System.Reflection.Emit. Создание новой сборки начинается с создания экземпляра класса AssemblyBuilder. Далее путем вызова методов класса AssemblyBuilder создается нужное количество модулей (объектов класса ModuleBuilder ), в модулях создаются типы (объекты класса TypeBuilder ), а в типах - конструкторы, методы и поля (объекты классов ConstructorBuilder, MethodBuilder и FieldBuilder, соответственно). То есть при генерации метаданных идеология такая же, как и при чтении их из готовой сборки (на рис. 4.6 представлена схема, показывающая последовательность создания метаданных).
В отличие от Metadata Unmanaged API, библиотека рефлексии содержит средства для генерации CIL-кода. Для этого предназначен класс ILGenerator. Он позволяет после создания объекта MethodBuilder (или ConstructorBuilder ) сгенерировать для соответствующего метода (или конструктора) поток инструкций.
В следующем примере библиотека рефлексии используется для генерации сборки .NET, состоящей из одного глобального метода main, выводящего на экран сообщение "Hello, World!":
using System;
using System.Threading;
using System.Reflection;
using System.Reflection.Emit;
class HelloGenerator
{
static void Main(string[] args)
{
AppDomain appDomain = Thread.GetDomain();
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = "hello.exe";
AssemblyBuilder assembly =
appDomain.DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.RunAndSave
);
ModuleBuilder module =
assembly.DefineDynamicModule(
"hello.exe", "hello.exe"
);
MethodBuilder mainMethod =
module.DefineGlobalMethod(
"main",
MethodAttributes.Static |
MethodAttributes.Public,
typeof(void),
null
);
MethodInfo ConsoleWriteLineMethod =
((typeof(Console)).GetMethod("WriteLine",
new Type[] { typeof(string) } ));
ILGenerator il = mainMethod.GetILGenerator();
il.Emit(OpCodes.Ldstr,"Hello, World!");
il.Emit(OpCodes.Call,ConsoleWriteLineMethod);
il.Emit(OpCodes.Ret);
module.CreateGlobalFunctions();
assembly.SetEntryPoint(
mainMethod,PEFileKinds.ConsoleApplication
);
assembly.Save("hello.exe");
}
}Сравнение возможностей библиотек
Возможности, предоставляемые библиотекой Metadata Unmanaged API и библиотекой рефлексии, представлены в таблице 4.3.
| Metadata Unmanaged API | Reflection API | |
|---|---|---|
| Чтение метаданных | + | + |
| Генерация метаданных | + | + |
| Чтение CIL-кода | - | - |
| Генерация CIL-кода | - | + |
Из таблицы следует, что ни одна из библиотек, поставляемых вместе с .NET Framework, не позволяет читать CIL-код, хотя эта возможность требуется целому ряду метаинструментов (например, верификаторам и оптимизаторам кода).

