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

Вызов конструкторов с параметрами, рассмотренный выше, является частным случаем динамического вызова методов. Вызов остальных методов производится без помощи дополнительных классов типа 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). Ниже будут подробнее рассмотрены другие способы динамического приведения типов.

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

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

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

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

 

// compile with csc /t:library FileProcessor.cs

using System;

namespace FileProc

{ interface IFileProcessor

{ bool IsSupport(string filePath);

}

}

 

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

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

 

// compile with csc /t:library TextProcessor.cs /r:FileProcessor.cs

using 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.cs

using 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.cs

using 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 и поместить его в ту же папку, что и остальные классы-обработчики.