Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Динамическое создание объекта и вызов методов

Поиск

Итак, мы умеем получать исчерпывающую информацию о типе, который ещё неизвестен в момент компиляции. На основе этой информации, имея экземпляр этого типа, мы умеем получать значения его полей и свойств. Для полного владения ситуацией нам остаётся только научиться создавать экземпляры этих типов, а потом ещё и вызывать их методы.

Создание объекта по его типу

При создании объекта вызывается его конструктор. Если вас устраивает вариант создания объекта с вызовом конструктора без аргументов (конструктора по умолчанию), то задача решается очень просто. Например, вот так:

class Class1{ int n = 5; static void Main() { string className = "App1.Class1"; Type type = Type.GetType(className); Object data = Activator.CreateInstance(type); Console.WriteLine(Trace1.ObjectFields("data", data)); Console.ReadLine(); }}

Для подобного рода вещей предназначен специальный класс Activator. У него есть набор статических методов CreateInstance, с помощью которых можно создавать объекты, не только имея объект Type, но также по имени сборки и имени класса, или по файлу сборки и имени класса. Среди методов CreateInstance есть и такие, которые позволяют использовать конструкторы с аргументами и задавать пользовательские атрибуты создаваемого класса.

Примеры использования различных вариантов Activator.CreateInstance

using System;using System.Reflection;using System.Diagnostics; namespace ConsoleApplication2{ class Class1 { public static string DotNetFrameworkDir() { Assembly testAssy = Assembly.Load("mscorlib.dll"); return System.IO.Path.GetDirectoryName(testAssy.Location); } static void Main() { // создаём объект по имени сборки и имени класса Object intObj = Activator.CreateInstance("mscorlib.dll", "System.Int32"); intObj = 5; Console.WriteLine(Trace2.ObjectFields("intObj", intObj)); // создаём объект по файлу сборки и имени класса Object rgn = Activator.CreateInstanceFrom(DotNetFrameworkDir() + "\\System.Drawing.dll", "System.Drawing.Region"); Console.WriteLine(Trace2.ObjectFields("rgn", rgn)); // Готовим параметры для вызова конструктора // Rectangle(Int32 x, Int32 y, Int32 width, Int32 height); object[] ctorArgs = { 10, 10, 100, 100 }; // Грузим сборку Assembly assembly = Assembly.LoadWithPartialName("System.Drawing"); Object rect = Activator.CreateInstance(assembly.FullName, "System.Drawing.Rectangle", false // ignoreCase, учитывать регистр!, 0, null // по умолчанию, ctorArgs // вот они - параметры!, null, null, null // всё остальное тоже по умолчанию); Console.WriteLine(Trace2.ObjectFields("rect", rect)); Console.ReadLine(); } }}
ПРИМЕЧАНИЕ Здесь применён простой способ получения пути к каталогу.Net Framework. Дело в том, что сборка mscorlib.dll используется в любой сборке, и потому всегда загружена. Остаётся только получить путь к папке, где лежит файл этой сборки, т.к. эта сборка всегда находится в каталоге.Net Framework.
     

Здесь приведён вариант создания объекта с использованием конcтруктора с параметрами. Составлен массив из параметров и передан в Activator.CreateInstance. Среди возможного множества конструкторов будет использован тот, который имеет в точности то же количество параметров и те же типы (и в том же порядке!), что и в переданном массиве параметров.

ПРИМЕЧАНИЕ Наряду с классом Activator способностью создавать объекты по их типу обладают классы Assembly, AppDomain и некоторые другие. У них тоже есть методы CreateInstance, работа с которыми ведётся подобным образом.

Динамический вызов методов

На самом деле мы только что уже занимались динамическим вызовом методов. С конструкторами, по крайней мере, разобрались. Вызов остальных методов производится без помощи дополнительных классов типа Activator. Этим занимается сам класс Type.

Динамический вызов метода динамически созданного объекта:

static void Main(){ // Получаем объект-тип для System.Drawing.Rectangle Assembly a = Assembly.LoadWithPartialName("System.Drawing"); AssemblyName an = a.GetName(); Type typeRect = Type.GetType("System.Drawing.Rectangle," + an.FullName, true); // Готовим параметры для вызова конструктора // Rectangle(Int32 x, Int32 y, Int32 width, Int32 height); object[] ctorArgs = { 10, 10, 100, 100 }; Object rect = Activator.CreateInstance(typeRect, ctorArgs, null); // Выводим поля динамически созданного объекта Console.WriteLine(Trace1.ObjectFields("rect", rect)); // Готовим параметры вызова метода Boolean Contains(Point pt); System.Drawing.Point point = new System.Drawing.Point(50, 50); object[] argContains = { point }; object contain = typeRect.InvokeMember("Contains", BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Instance, null, rect, argContains); Console.WriteLine(contain.Equals(true)? "contains": "does not contain"); Console.WriteLine(Trace1.ObjectFields("point", point)); Console.ReadLine();}

Если вызываемый метод перегружен, будет вызван тот вариант метода, который имеет те же типы параметров, причем в том же порядке, что и типы элементов переданного массива параметров.

Использование интерфейсов

Для вызова методов динамически созданного объекта можно применять гораздо более удобный и эффективный способ. Правда, этот способ работает, только если вы знаете, что объект реализует известный вам на момент компиляции интерфейс.

Использование динамически созданного объекта через интерфейс

static void Main(){ // Получаем объект-тип для System.Drawing.SolidBrush Assembly a = Assembly.LoadWithPartialName("System.Drawing"); AssemblyName an = a.GetName(); Type typeBrush = Type.GetType("System.Drawing.SolidBrush," + an.FullName, true); // Готовим параметры для вызова конструктора // SolidBrush(Color color); object[] ctorArgs2 = { System.Drawing.Color.Blue }; Object brush = Activator.CreateInstance(typeBrush, ctorArgs2, null); // Выводим поля динамически созданного объекта Console.WriteLine(Trace1.ObjectFields("brush", brush)); // Получаем ссылку на интерфейс ICloneable cloner = brush as ICloneable; if (cloner!= null) { Object brushCloned = cloner.Clone(); Console.WriteLine(Trace1.ObjectFields("brushCloned", brushCloned)); } Console.ReadLine();}

Очевидно, что эффективность вызова метода динамически созданного объекта через интерфейс гораздо выше, чем через InvokeMember, ведь это обычный вызов виртуального метода. Поэтому, если есть такая возможность, лучше использовать интерфейсы.

Для получения ссылки на интерфейс ICloneable здесь применяется операция as языка C#. Эта операция возвращает ссылку на интерфейс, если он реализован объектом. Если объект не поддерживает данный интерфейс, будет возвращена пустая ссылка (null). Ниже будут подробнее рассмотрены другие способы динамического приведения типов.

Позднее связывание

Ну вот, теперь можно сказать, что мы умеем работать с типами, определяемыми в момент исполнения, почти столь же свободно, как и с типами, известными нам на момент компиляции. (Если я и преувеличил, то совсем чуть-чуть. 8-). Теперь мы вправе заслуженно насладиться плодами вновь освоенных технологий. Предлагаю вам в качестве небольшой передышки рассмотреть позднее связывание.

Традиционная постановка задачи позднего связывания: предположим, у нас есть программа, позволяющая выполнять обработку каких-либо файлов. Для обработки используются классы, реализующие интерфейс FileProcessor. Список типов обрабатываемых программой файлов и соответствующих им классов-обработчиков постоянно пополняется. Сборки, содержащие новые классы-обработчики, помещаются в отдельную папку, а программа-обработчик файлов должна уметь использовать всё новые и новые классы-обработчики без перекомпиляции.

Не сомневаюсь, что многие из читающих эти строчки решали подобные задачи. Причем в разные времена эти задачи решались по-разному, начиная от LoadLibrary и GetProcAddress, и кончая COM-интерфейсами и категориями. Посмотрим, какое решение этой классической задачи предлагает технология.Net.

Интерфейс класса-обработчика будет иметь единственную функцию, определяющую, может ли данный класс обработать данный файл. Поместим интерфейс в отдельную сборку. На неё будут ссылаться конкретные классы-обработчики.

Интерфейс-обработчик.

// compile with csc /t:library FileProcessor.cs using System;namespace FileProc{ interface IFileProcessor { bool IsSupport(string filePath); }}

Теперь определим пару классов обработчиков. Один из них будет работать с текстовыми файлами, второй – с картинками в формате ВMP. При этом каждый будет реализовывать интерфейс-обработчик, и будет помещён в отдельную сборку, ссылающуюся на сборку с интерфейсом (см. рис.3).

Обработчик текстовых файлов.

// compile with csc /t:library TextProcessor.cs /r:FileProcessor.csusing System;using FileProc;namespace TextProcessor{ public class TextProcessor: IFileProcessor { override public bool IsSupport(string filePath) { return ".txt" == IO.Path.GetExtension(filePath).ToLower(); } }}

Обработчик картинок.

// compile with csc /t:library BmpProcessor.cs /r:FileProcessor.csusing System;using FileProc;namespace BmpProcessor{ public class BmpProcessor: IFileProcessor { override public bool IsSupport(string filePath) { return ".bmp" == IO.Path.GetExtension(filePath).ToLower(); } }}

 

В самом приложении, осуществляющем позднее связывание с классами-обработчиками, для вас уже почти ничего нового нет. Здесь просто загружаются все сборки из каталога приложения. Затем запрашиваются все типы из каждой сборки, и среди типов ищутся те, что унаследованы от абстрактного класса-обработчика. После того, как нужный тип найден, создаётся его экземпляр и вызывается метод IsSupport.

Приложение, использующее позднее связывание с классами-обработчиками.

// compile with csc LateBind.cs /r:FileProcessor.csusing System;using System.IO;using System.Reflection;using FileProc; namespace LateBind{ class Class1 { [STAThread] static void Main(string[] args) { if(args.Length < 1) return; string[] files = Directory.GetFiles(System.Environment.CurrentDirectory, "*.dll"); foreach (string file in files) { Assembly assembly = Assembly.LoadFrom(file); Type[] types = assembly.GetTypes(); foreach (Type type in types) { if (typeof(IFileProcessor).IsAssignableFrom(type) &&! type.IsAbstract) { IFileProcessor processor = (IFileProcessor)Activator.CreateInstance(type); if (processor.IsSupport(args[0])) { Console.WriteLine("Assembly {0} can process file {1}.", assembly.GetName().Name, args[0]); Console.ReadLine(); return; } } } } Console.WriteLine("Can't process file {0}.", args[0]); Console.ReadLine(); } }}

Теперь, если появится необходимость обрабатывать файлы, например, формата VRML, не потребуется дорабатывать и перекомпилировать приложение. Достаточно будет разработать соответствующий класс-обработчик VRMLProcessor и поместить его в ту же папку, что и остальные классы-обработчики.



Поделиться:


Последнее изменение этой страницы: 2016-07-14; просмотров: 337; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.117.172.252 (0.005 с.)