Применение рефлексии для создания плагинов
ОГЛАВЛЕНИЕ
Страница 1 из 3
Плагины стали неотъемлемой частью больших коммерческих приложений. С их помощью можно наращивать функциональность приложений без повторной компиляции или быстро изменять бизнес-правила, на основе которых работает приложение. Кроме того, для разработки плагинов не нужно иметь доступа к исходному коду приложения, поэтому они могут разрабатываться сторонними организациями. В .NET написание плагинов является простой задачей, которая решается с помощью рефлексии (reflection). Рефлексия позволяет динамически загружать сборки, получать информацию о методах, свойствах, событиях и полях классов из сборок, создавать новые типы и вызывать методы во время выполнения. Классы и интерфейсы для рефлексии находятся в пространстве имен System.Reflection. В этой статье мы рассмотрим создание плагинов и их подключение к приложению с помощью т.н. позднего связывания. Со сборками, в которых будут находится плагины, мы будем работать с помощью класса Assembly. Сборка может быть загружена с помощью статических методов класса Assembly Load, LoadFrom и LoadWithPartialName. Load загружает сборку по ее имени, заданным строкой, или на основе информации хранящейся в объекте AssemblyName (версия, криптографический ключ, ифнормация о культуре). В имя сборки не входит расширения файла, в котором она находится. Например, имя сборки (MyAsm.dll будет MyAsm). LoadFrom напрямую загружает сборку из файла, путь к которому передается методу. Метод LoadWithPartialName загружает сборку при неполных сведениях о ней, но пользоваться им не рекомендуется из-за непредсказуемости его работы, т.к. он был разработан для бетта-тестеров .NET Framework. Можно загружать сборки и вызовом метода Load для объектов домена AppDomain. Например, чтобы загрузить сборку в текущий домен можно воспользоваться таким кодом AppDomain.CurrentDomain.Load(assemblyName);
Основной класс для динамического получения информации о классах, интерфейсах, их полях, методах и перечислениях - Type. Для получения объекта Type можно воспользоваться несколькими разными методами:- статический метод Type.GetType, который по имени типа возвращает объект Type
- методы GetInterface, GetInterfaces, FindInterfaces, GetElementType и GetTypeArray класса Type
- методы GetType, GetTypes и GetExportedTypes класса Assembly
- методы GetType, GetTypes и FindTypes класса Module
- оператор typeof
Создание экземпляров типов
По объекту Type можно не только определять параметры типа, но и создавать его экземпляры и вызывать их методы. Для этого также существует несколько методов:- методы CreateInstance, CreateInstanceAndUnrap, CrateInstanceFrom и CrateInstanceFromAndUnrap класса AppDomain. После вызова методов, названия которых не оканчиваются на AndUnrap, для доступа к реальным данным нужно вызывать дополнительную функцию Unrap, т.к. эти методы возвращают wrapper (объект класса ObjectHandle) для нового экземляра типа
- методы CreateInstance и CreateInstanceFrom класса Activator. Это специальный класс для создания экземпляров типов и получения ссылок на удаленные объекты. Методу CreateInstance передаются объект Type или название инстанцируемого типа, массив объектов, соответствующих параметрам конструктора типа и объекты CultureInfo. Методу CreateInstanceFrom дополнительно передается имя сборки, содержащий тип. Методы, не принимающие в качестве параметра объект Type, также возвращают wrapper's ObjectHandle
- метод CreateInstance класса Assembly, создающий тип по его имени
- метод Invoke класса ContructorInfo
- метод InvokeMember класса Type
Использование интерфейсов
При создании плагинов обычно используются интерфейсы, определяющие методы и свойства, которые должны реализовываться плагином. Для получения интерфейсов, которые есть у типа, используются методы GetInterface,GetInterfaces и FindInterfaces класса Type. Метод GetInterface по имени интерфейса позвращает объект Type для этого интерфейса или null если такого интерфейса у типа нет. Метод GetInterfaces возвращает массив объектов Type с информацией об интерфейсах. Метод FindInterfaces возвращает массив интерфейсов, выбранных с помощью фильтра - делегата, вызываемого для каждого интерфейса.Если класс реализует несколько интерфейсов, у которых есть методы с одинаковыми названиями, то нужно использовать метод GetInterfaceMap класса Type. Он возвращает объект InterfaceMapping для определения соотношения методов интерфейсов и методов класса, которые их реализуют.
Вызов методов
Обычно методы вызываются с помощью метода InvokeMember класса Type. Процесс вызова метода состоит из двух этапов - привязки, при котором находится нужный метод, и непосредственно вызова. Для вызова нужно указать- имя метода (в качестве метода может быть обычный метод, конструктор, свойство или поле)
- битовую маску из значений BindingFlags для поиска метода. В маске можно указать тип доступа метода, тип метода (поле, свойство, ...), тип данных и пр.
- объект Binder для связывания членов и аргументо
- объект, у которого вызывается метод
- массив аргументов метода
- массив объектов ParameterModifier
- объект CultureInfo
Разработка плагинов
Для демонстрации применения рефлексии при создании плагинов было разработано небольшое тестовое приложение, состоящее из 4 проектов.- MainApp - основное приложение, к которому будут подключаться плагины. Приложение загружает из графических файлов изображения и выводит их на форме
- Interface - определяет интерфейсы IPlugin для плагинов и IMainApp для приложений, к которым будут подключаться плагины
- RandomPlugin и ReversePlugin - плагины для добавления шума к изображениям и отражения изображения по вертикали
public interface IMainApp
{
Bitmap Image { get; set; }
}
public interface IPlugin
{
string Name { get; }
string Version { get; }
string Author { get; }
void Transform(IMainApp app);
}
Если бы наше приложение использовало какие то типы (классы, интерфейсы, перечисления, ...), которые бы использовались или передавались плагинам, то их тоже нужно было бы поместить в сборку Interface.