|
Reflection в
.NET
Оригинал
статьи Автор: Олег Степанов
Введение
Долгое время люди писали программы, которые
компилировали непосредственно в машинный код, связывали модули и получали
исполняемые файлы, в которых вся внутренняя структура программы (особенно
если эта программа была написана на объектно-ориентированном языке, таком
как C++).
Если нужно было делать динамические вызовы,
изучать (или даже изменять) внутреннюю структуру программы во время
выполнения, приходилось прибегать к нетривиальным методикам.
С появлением виртуальных сред выполнения
(virtual execution environments), первой популярной из которых стала
виртуальная машина Java (Java Virtual Machine, JVM), ситуация резко
изменилась к лучшему. Появился набор средств, позволяющих динамически
создавать экземпляры классов, вызывать их методы, подгружать
дополнительные классы и т.д. Все это очень облегчало создание
соответствующих приложений. Этот комплекс средств был назван reflection
(от англ. "отражение")
При проектировании .NET, компания Microsoft
не могла не реализовать подобную функциональность в своей CLR. Более того,
возможности reflection в .NET Framework во многом превосходят аналогичные
в JDK.
В этой статье я кратко разберу лишь
основные возможности Reflection (динамическая загрузка сборок,
динамическое инстанцирование, динамический вызов методов, работа с
атрибутами). Если читатель заинтересуется, полную информацию всегда можно
найти в MSDN.
Динамическая загрузка сборок
В обычной программе, если необходимо
использовать классы из внешних сборок, в манифесте вашей сборки
проставляется ссылка на необходимую внешнюю сборку. Благодаря reflection,
появилась возможность динамически загружать любые сборки и использовать
классы из них.
Эта функциональность реализуется в классе
Assembly из пространства имен System.Reflection (все классы для работы с
reflection находятся в этом namespace).
Чтобы загрузить сборку, нужно вызвать метод
Load (для загрузки из COFF-образа или по отображаемому имени) или LoadFrom
(для загрузки из файла).
Для пояснений рассмотрим небольшую
программку. Она состоит из двух частей. Первая представляет собой сборку,
которую мы будем динамически использовать. Вот ее код:
using System;
namespace SampleClass
{
public class MyCustomAttribute : Attribute
{
string _url;
public string URL
{
get
{
return _url;
}
set
{
_url = value;
}
}
public MyCustomAttribute( string url )
{
URL = url;
}
}
public class DynamicClass
{
public DynamicClass()
{
}
[MyCustomAttribute("http://localhost")]
public void DynamicMethod( string name )
{
Console.WriteLine("Hello, my name is " + name);
}
}
}
|
Вторая представляет собой динамический
загрузчик для первой. Мы будем постепенно расширять ее код, реализуя новые
возможности, но начнем с простой загрузки сборки.
using System;
using System.Reflection;
namespace DynamicLoader
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
Assembly assembly = Assembly.LoadFrom("SampleClass.dll");
Console.WriteLine("Assembly name: " + assembly.FullName);
}
}
}
|
Если запустить программу на выполнение, то
она выведет следующую строчку:
Assembly name: SampleClass, Version=1.0.786.22185, Culture=neutral, PublicKeyToken=null
|
, что свидетельствует об успешной загрузке
сборки. Но сама по себе загрузка сборок ничего не стоит - не просто же так
мы их загружаем. Итак, следующая часть:
Динамическое инстанцирование
В этой части мы научимся создавать
экземпляры классов из сборки. Для этого служит метод CreateInstance уже
известного нам класса Assembly. Этот метод получает на вход имя типа,
которое нужно инстанцировать и возвращает его экземпляр. Заметим, что этот
метод может быть использован лишь для вызова конструктора без параметров.
Для вызова конструктора с параметрами нужно использовать более продвинутую
технику, которую читателю предлагается изучить самостоятельно. Итак,
немного изменим код загрузчика.
using System;
using System.Reflection;
namespace DynamicLoader
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
Assembly assembly = Assembly.LoadFrom("SampleClass.dll");
Console.WriteLine("Assembly name: " + assembly.FullName);
object dyn = assembly.CreateInstance("SampleClass.DynamicClass");
Console.WriteLine("Type name: " + dyn.GetType().FullName);
}
}
}
|
Жирным выделены новые строчки. Первая
создает экземпляр класса по его имени (не забудьте указать namespace), а
вторая выводит на экран имя типа для проверки). Вывод этой программы
следующий:
Assembly name: SampleClass, Version=1.0.786.22185, Culture=neutral, PublicKeyToken=null
Type name: SampleClass.DynamicClass
|
Итак, мы успешно создали экземпляр класса с
помощью средств reflection. Теперь попробуем вызвать метод этого
класса.
Динамический вызов методов
Теперь мы пришли к самому интересному -
вызову методов. Не зря же мы загружали сборки, создавали экземпляры
классов. Пора заставить их работать на нас! Для вызова методов нужно
использовать возможности класса System.Type (хотя он и не принадлежит
пространству имен System.Reflection, к reflection он имеет самое что ни на
есть непосредственное отношение - его возможности в этом смысле огромны,
стоит хотя бы отметить, что именно с помощью него можно вызывать
конструкторы с параметрами). Но в этой части мы не будем вызывать
конструкторы - мы будем вызывать методы. Для вызова метода нужно сначала
получить информацию об этом методе в виде экземпляра класса MethodInfo с
помощью метода GetMethod класса Type, а затем вызвать метод Invoke этого
класса. Покажем как это делается на примере:
using System;
using System.Reflection;
namespace DynamicLoader
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
Assembly assembly = Assembly.LoadFrom("SampleClass.dll");
Console.WriteLine("Assembly name: " + assembly.FullName);
object dyn = assembly.CreateInstance("SampleClass.DynamicClass");
Console.WriteLine("Type name: " + dyn.GetType().FullName);
Type dynType = dyn.GetType();
MethodInfo method = dynType.GetMethod("DynamicMethod");
method.Invoke(dyn, new object[] {"Jack"});
}
}
}
|
Здесь, как видно, мы получаем сначала тип
только что созданного экземпляра, затем получаем его метод с именем
DynamicMethod и, наконец, вызываем его с параметром "Jack". Посмотрим, что
выведет наша программа:
Assembly name: SampleClass, Version=1.0.786.22767, Culture=neutral, PublicKeyToken=null
Type name: SampleClass.DynamicClass
Hello, my name is Jack
|
Вау! Мы сделали это! Все работает.
Вроде как мы просмотрели основные
возможности Reflection - мы загрузили сборку, создали экземпляр класса из
нее и вызвали его метод. Но это еще не все - я просто не могу хотя бы
вскользь не коснуться получения атрибутов у динамически загруженных
классов. Подробнее об атрибутивном программировании можно почитать в
соответствующей статье на dotSITE, здесь я лишь покажу как получить
динамический атрибут у динамически созданного экземпляра. По пути мы также
научимся получать значения свойств класса.
Получение атрибутов и свойств
Для получения атрибутов у класса, сборки,
свойства или метода, нужно вызвать метод GetCustomAttributes у класса
Assembly, Type, PropertyInfo и MethodInfo соответственно. Этот метод
возвращает массив атрибутов. Для лучшего понимания посмотрим код:
using System;
using System.Reflection;
namespace DynamicLoader
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
Assembly assembly = Assembly.LoadFrom("SampleClass.dll");
Console.WriteLine("Assembly name: " + assembly.FullName);
object dyn = assembly.CreateInstance("SampleClass.DynamicClass");
Console.WriteLine("Type name: " + dyn.GetType().FullName);
Type dynType = dyn.GetType();
MethodInfo method = dynType.GetMethod("DynamicMethod");
method.Invoke(dyn, new object[] {"Jack"});
object[] attrs = method.GetCustomAttributes(false);
foreach (Attribute attr in attrs)
{
Type attrtype = attr.GetType();
Console.WriteLine("Attribute: " + attrtype.FullName);
PropertyInfo[] properties = attrtype.GetProperties();
foreach (PropertyInfo property in properties)
{
Console.WriteLine("{0} = {1}", property.Name, property.GetValue(attr, new object[] {}));
}
}
}
}
}
|
Здесь мы не только получили список
атрибутов, но и еще для каждого атрибуте получили значения всех свойств.
Поясню, как это делается. Сначала получаем тип атрибута, затем, используя
метод GetProperties, получаем список всех свойств и, наконец, получаем
значение каждого свойства для данного экземпляра атрибута. Чтобы проверить
правильность работы программы, посмотрим на ее вывод:
Assembly name: SampleClass, Version=1.0.786.23235, Culture=neutral, PublicKeyToken=null
Type name: SampleClass.DynamicClass
Hello, my name is Jack
Attribute: SampleClass.MyCustomAttribute
URL = http://localhost
TypeId = SampleClass.MyCustomAttribute
|
Как мы видим, все работает. Дополнительный
атрибут TypeId присутствует в экземплярах классов незримо.
Заключение
Итак, мы показали, что средства reflection
в .NET представляют огромные возможности для программистов. Более того,
то, о чем мы здесь рассказали, составляет лишь малую часть от возможностей
.NET - от динамической загрузки целык комплексов сборок и вызова
конструкторов с параметрами до динамической генерации кода.
© 2001 Корпорация
Microsoft. Все права защищены. Правила
использования | Политика
защиты | Кодекс
поведения
|
|