Атрибуты, сборки, рефлексия
Класс сборки в действии
Исследование свойств и областей применения класса Assembly начинаем с создания тестовой однофайловой сборки AssemblyForStart в рамках проекта Class Library.
Первая сборка Operators00.exe:
using System; namespace Operators00 { public class xPoint { float x, y; xPoint() { x = 0.0F; y = 0.0F; } public xPoint(float xKey, float yKey):this() { x = xKey; y = yKey; } public static bool operator true(xPoint xp) { if (xp.x != 0.0F && xp.y != 0.0F) return true; else return false; } public static bool operator false(xPoint xp) { if (xp.x == 0.0F || xp.y == 0.0F) return false; else return true; } public static xPoint operator | (xPoint key1, xPoint key2) { if (key1) return key1; if (key2) return key2; return new xPoint(); } public static xPoint operator & (xPoint key1, xPoint key2) { if (key1 && key2) return new xPoint(1.0F, 1.0F); return new xPoint(); } public void Hello() { Console.WriteLine("Hello! Point {0},{1} is here!",this.x,this.y); } } class Class1 { // The main entry Point for the application. static void Main() // У точки входа пустой список параметров. // Я пока не сумел ей передать через метод Invoke массива строк. { xPoint xp0 = new xPoint(1.0F, 1.0F); xPoint xp1 = new xPoint(1.0F, 1.0F); if (xp0 || xp1) Console.WriteLine("xp0 || xp1 is true!"); else Console.WriteLine("xp0 || xp1 is false!"); } } }Листинг 10.3.
Вторая сборка AssemblyForStart.dll. В примере она так и не запускалась. Используется только для тестирования стандартных средств загрузки сборок – библиотек классов:
using System; namespace AssemblyForStart { // Class1 : первая компонента сборки AssemblyForStart. public class Class1 { public Class1() { Console.WriteLine("This is constructor Class1()"); } public void fC1() { Console.WriteLine("This is fC1()"); } } }
А вот полигон AssemblyStarter.exe. В примере демонстрируется техника ПОЗДНЕГО СВЯЗЫВАНИЯ. Именно поэтому код, который выполняется после загрузки сборки, не содержит в явном виде информации об используемых в приложении типах:
using System; using System.Reflection; using System.IO; namespace AssemblyStarter { // Приложение обеспечивает запуск сборки. class Class1 { static void Main(string[] args) { // Сборка может быть вызвана непосредственно по имени // (строковый литерал с дружественным именем сборки). // Ничего особенного. Просто имя без всяких там расширений. // Главная проблема заключается в том, что сборки должны // предварительно включаться в раздел References (Ссылки). // Кроме того, информация о загружаемой сборке может быть представлена // в виде объекта - представителя класса AssemblyName, ссылка // на который также может быть передана в качестве аргумента методу // Assembly.Load(). // Вот здесь как раз и происходит формирование этого самого объекта. AssemblyName asmName = new AssemblyName(); asmName.Name = "AssemblyForStart"; // Версию подсмотрели в манифесте сборки с помощью IlDasm.exe. Version v = new Version("1.0.1790.25124"); // Можно было бы для пущей крутизны кода поле Version // проинициализировать непосредственно (дело хозяйское): // asmName.Version = new Version("1:0:1790:25124"); asmName.Version = v; // Ссылка на объект - представитель класса Assembly. // Assembly asm = null; try { // Загрузка сборки по "дружественному имени". //asm = Assembly.Load("AssemblyForStart"); // Путь и полное имя при загрузке частной сборки не имеют значения. // Соответствующие файлы должны располагаться непосредственно // в каталоге приложения. //asm = Assembly.Load //(@"D:\Users\WORK\Cs\AssemblyTest\AssemblyForStart\bin\Debug\ AssemblyForStart.dll"); //asm = Assembly.Load(asmName); // Если сборку организовать в виде исполняемого модуля и // "запихнуть" в каталог вручную – загрузится и такая сборка. asm = Assembly.Load("Operators00"); } catch(FileNotFoundException e) { Console.WriteLine("We have a problem:" + e.Message); } // Итак, решено. Загрузили сборку, содержащую объявление класса xPoint. // Если сборка загрузилась – с ней надо что-то делать. Ясное дело, // надо выполнять программный код сборки. // Первый вариант выполнения. В сборке содержатся объявления двух классов: // класса xPoint и класса Class1. Мы воспользуемся объявлением класса // xPoint, построим соответствующий объект - представитель этого класса, // после чего будем вызывать нестатические методы - члены этого класса. // Все происходит в режиме ПОЗДНЕГО связывания. // Поэтому ни о каких ЯВНЫХ упоминаниях // имен типов не может быть речи. // Единственное явное упоминание – это упоминание // имени метода. object[] ps = {25,25}; Type[] types = asm.GetTypes(); // Здесь используется класс Activator! object obj = Activator.CreateInstance(types[0],ps); MethodInfo mi = types[0].GetMethod("Hello"); mi.Invoke(obj,null); // Второй вариант выполнения. Воспользуемся тем обстоятельством, // что загружаемая сборка не является библиотекой классов, а является // обычной выполнимой сборкой с явным образом обозначенной точкой // входа – СТАТИЧЕСКОЙ функцией Main(), которая является членом // класса Class1. mi = asm.EntryPoint; // Вот, все получилось! Единственное, что я не сделал, // так это не смог передать в точку входа загруженной // сборки массива строк-параметров (смотреть на точку входа данной // сборки). Ну не удалось. Потому и в сборке // Operators точку входа сборки объявил без параметров. mi.Invoke(null,null); } } }Листинг 10.4.
Собрали, запустили. Получилось. Стало быть, ВСЕ ХОРОШО.
В ходе выполнения приложения класс Assembly позволяет:
- получать информацию о самой сборке;
- обращаться к членам класса, входящим в сборку;
- загружать другие сборки.
Разбор полетов
В приведенном выше примере демонстрируется техника ПОЗДНЕГО СВЯЗЫВАНИЯ. Именно поэтому код, который выполняется после загрузки сборки, не содержит в явном виде информации об используемых в приложении типах. Транслятор действует в строгом соответствии с синтаксисом языка C# и просто не поймет пожелания "создать объект – представитель класса ..., который будет объявлен в сборке, которую предполагается загрузить в ходе выполнения приложения".
Класс System.Activator
Класс Activator – главное средство, обеспечивающее позднее связывание.
Содержит методы, позволяющие создавать объекты на основе информации о типах, получаемой непосредственно в ходе выполнения приложения, а также получать ссылки на существующие объекты. Далее приводится список перегруженных статических методов класса:
- CreateComInstanceFrom. Создает экземпляр COM-объекта;
-
CreateInstance. Создает объект – представитель specified-типа, используя при этом наиболее подходящий по списку параметров конструктор (best matches the specified parameters).
Пример:
ObjectHandle hdlSample; IMyExtenderInterface myExtenderInterface; string argOne = "Value of argOne"; int argTwo = 7; object[] args = {argOne, argTwo}; // Uses the UrlAttribute to create a remote object. object[] activationAttributes = {new UrlAttribute("http://localhost:9000/MySampleService")}; // Activates an object for this client. // You must supply a valid fully qualified assembly name here. hdlSample = Activator.CreateInstance( "Assembly text name, Version, Culture, PublicKeyToken", "samplenamespace.sampleclass", true, BindingFlags.Instance|BindingFlags.Public, null, args, null, activationAttributes, null); myExtenderInterface = (IMyExtenderInterface)hdlSample.Unwrap(); Console.WriteLine(myExtenderInterface.SampleMethod("Bill"));
-
CreateInstanceFrom. Создает объект – представитель типа, специфицированного по имени. Имя специфицируется на основе имени сборки. При этом используется подходящий по списку параметров конструктор (the constructor that best matches the specified parameters).
Пример:
ObjectHandle hdlSample; IMyExtenderInterface myExtenderInterface; object[] activationAttributes = {new SynchronizationAttribute()}; // Assumes that SampleAssembly.dll exists in the same directory as this assembly. hdlSample = Activator.CreateInstanceFrom( "SampleAssembly.dll", "SampleNamespace.SampleClass", activationAttributes); // Assumes that the SampleClass implements an interface provided by // this application. myExtenderInterface = (IMyExtenderInterface)hdlSample.Unwrap(); Console.WriteLine(myExtenderInterface.SampleMethod("Bill"));
- GetObject – Overloaded. Создает прокси (заместителя, представителя) для непосредственного запуска активизированного сервером объекта или XML-сервиса.
Сборка может быть вызвана непосредственно по имени (строковый литерал с дружественным именем сборки).
Ничего особенного. Просто имя без всяких там расширений. Главная проблема заключается в том, что частные сборки должны предварительно включаться в раздел References (Ссылки).
Кроме того, информация о загружаемой сборке может быть представлена в виде объекта – представителя класса AssemblyName, ссылка на который также может быть передана в качестве аргумента методу Assembly.Load().