Г. Шилдт - Полный справочник по C# (1160789), страница 87
Текст из файла (страница 87)
Компоновочные файлы также можно найти среди файлов динамически подключаемой библиотеки (dynamic link library — DLL), которые имеют расширение d l l . Например, файлMyClasses. cs можно скомпилировать с помощью такой командной строки:I esc / t : l i b r a r y MyClasses.esВ результате выполнения этой команды мы получили бы выходной файлMyClasses.dll.
При внесении программного кода в DLL-библиотеку не требуетсясоздавать метод Main(). Для всех ехе-файлов наличие такой точки входа, как методMain (), обязательно. Поэтому класс Demo содержит заглушку для метода Main (). ДляDLL-библиотек точки входа могут отсутствовать. Если вы захотите превратитьMyClass в DLL-файл, вам придется изменить обращение к методу LoadFromO следующим образом:I Assembly asm = Assembly.LoadFrom("MyClasses.dll");Полная автоматизация получения информации о типахПрежде чем завершить изучение темы отражения информации о типах, стоит рассмотреть еще один пример. Несмотря на то что в предыдущей программе нам удалосьиспользовать класс MyClass без явного указания его имени в программе, все же мыпредварительно знали содержимое класса MyClass.
Например, нам были заранее известны имена его методов ( s e t ( ) и sum О). Однако с помощью средства отраженияможно использовать тип, о котором нам предварительно ничего не известно. Дляэтого необходимо получить информацию, необходимую для создания объекта, и сгенерировать вызовы методов. Такой подход эффективен, например, в случае визуального средства проектирования, поскольку в нем используются типы, имеющиеся всистеме.Чтобы понять, как динамически извлечь информацию о типе, рассмотрим следующий пример, в котором загружается компоновочный файл MyClasses.exe, создается объект класса MyClass, а затем вызываются все объявленные методы без какихбы то ни было предварительных сведений.// Использование класса MyClass без опоры на// предварительные данные о нем.using System;using System.Reflection;class ReflectAssemblyDemo {public s t a t i c void Main() {int val;Assembly asm = Assembly.LoadFrom("MyClasses.exe");Type[] alltypes = asm.GetTypes();Type t = a l l t y p e s [ 0 ] ; // Используем первый// обнаруженный класс.Console.WriteLine("Используем: " + t.Name);Constructorlnfo[] ci = t.GetConstructors();// Используем первый обнаруженный конструктор.Глава 17.
Динамическая идентификация типов, отражение и атрибуты*471Parameterlnfof] cpi = ci[0].GetParameters();object reflectOb;if(cpi.Length > 0) {object[] consargs = new object[cpi.Length];// Инициализируем аргументы.for(int n=0; n < cpi.Length; n++)consargs[n] = 10 + n * 20;// Создаем объект.reflectOb = ci[0].Invoke(consargs) ;} elsereflectOb = ci[0].Invoke(null);Console.WriteLine("ХпВызываем методы для объекта reflectOb.");Console.WriteLine();// Игнорируем унаследованные методы.Methodlnfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly |BindingFlags.Instance |BindingFlags.Public) ;// Вызываем каждый метод.foreach(Methodinfо m in mi) {Console.WriteLine("Вызов метода {0} ", m.Name);// Получаем параметры.Parameterlnfo[] pi = m.GetParameters();// Выполняем методы,switch(pi.Length) {case 0: // без аргументовif(m.ReturnType == typeof(int)) {val = (int) m.Invoke(reflectOb, null);Console.WriteLine("Результат равен " + val);}else if(m.ReturnType == typeof(void)) {m.Invoke(reflectOb, null);}break;case 1: // один аргументif(pif0].ParameterType == typeof(int)) {object [] args = new object[1];args[0] = 14;if((bool) m.Invoke(reflectOb, args))Console.WriteLine("14 находится между х и у .
" ) ;elseConsole.WriteLine("14 не находится между х и у . " ) ;}break;case 2: // два аргументаif((pi[0].ParameterType == typeof(int)) &&472Часть I. Язык С#(pi[1].ParameterType == t y p e o f ( i n t ) ) )o b j e c t [ ] a r g s = new o b j e c t [ 2 ] ;a r g s [ 0 ] = 9;a r g s [ 1 ] = 18;m.Invoke(reflectOb, a r g s ) ;{}e l s e i f ( ( p i [ 0 ] . P a r a m e t e r T y p e == t y p e o f ( d o u b l e ) ) &&(pi[1].ParameterType == t y p e o f ( d o u b l e ) ) ) {o b j e c t [ ] a r g s = new o b j e c t [ 2 ] ;a r g s [ 0 ] = 1.12;args[1] = 23.4;m.Invoke(reflectOb, a r g s ) ;}break;}Console.WriteLine();Результаты выполнения этой программы таковы:Используем: MyClassСоздание объекта по формату M y C l a s s ( i n t ) .Значение х: 10, значение у: 10Вызываем методы для объекта'reflectOb.Вызов метода sumРезультат равен 20Вызов метода isBetween14 не находится между х и у.Вызов метода s e tВнутри метода s e t ( i n t , i n t ) .
Значение х: 9, значение у: 18Вызов метода s e tВнутри метода s e t ( d o u b l e , d o u b l e ) . Значение х: 1, значение у: 23Вызов метода showЗначение х: 1, значение у: 23Обратите внимание вот на что. Во-первых, программа получает (и использует) информацию только о тех методах, которые явно объявлены в классе MyClass. Этафильтрация достигается благодаря использованию BindingFlags-формата вызова метода GetMethods (). Тем самым становится возможным отсев унаследованных методов.
Во-вторых, отметьте, каким образом программа динамически получает количество параметров и тип значений, возвращаемых каждым методом. Количество параметров определяется с помощью switch-инструкции. В каждой case-ветви этойинструкции проверяется тип (типы) параметра (параметров) и тип возвращаемого методом значения. Затем на основе этой информации и организуется соответствующийвызов метода.Глава 17.
Динамическая идентификация типов, отражение и атрибуты473АтрибутыВ С# предусмотрена возможность вносить в программу информацию описательного характера в формате атрибута. Атрибут содержит дополнительные сведения оклассе, структуре, методе и т.д. Например, можно создать атрибут, определяющий типкнопки, для отображения которой предназначен класс. Атрибуты указываются внутриквадратных скобок, предваряя элемент, к которому они применяются. Таким образом,атрибут не является членом класса.
Он просто содержит дополнительную информацию об элементе.Основы применения атрибутовАтрибут поддерживается классом, производным от класса System. A t t r i b u t e . Таким образом, все классы атрибутов являются подклассами класса A t t r i b u t e . Хотякласс класс A t t r i b u t e определяет фундаментальную функциональность, она не всегда востребована при работе с атрибутами. Для классов атрибутов принято использовать суффикс A t t r i b u t e . Например, для класса атрибута, предназначенного для описания ошибок, вполне подошло бы имя E r r o r A t t r i b u t e .Объявление класса атрибута предваряется атрибутом A t t r i b u t e U s a g e . Этот встроенный атрибут задает типы элементов, к которым может применяться объявляемыйатрибут.Создание атрибутаВ классе атрибута определяются члены, которые поддерживают данный атрибут.Обычно классы атрибутов очень просты и содержат лишь небольшое количество полейили свойств.
Например, в атрибуте может содержаться комментарий, описывающийэлемент, к которому относится атрибут. Такой атрибут может иметь следующий вид:[AttributeUsage(AttributeTargets.All)]public class RemarkAttribute : Attribute {string pri_remark; // Базовое поле для свойства remark.public RemarkAttribute(string comment) {pri_remark = comment;}public string remark {get {return pri_remark;}Рассмотрим представленный класс построчно.Имя этого атрибута RemarkAttribute.
Его объявление предваряется атрибутомA t t r i b u t e U s a g e , который означает, что атрибут RemarkAttribute можно применитьк элементам всех типов. С помощью атрибута A t t r i b u t e U s a g e можно сократить список элементов, к которым будет относиться атрибут; эту возможность мы рассмотримв следующей главе.Затем следует объявление класса атрибута RemarkAttribute, производного отA t t r i b u t e . Класс RemarkAttribute содержит одно закрытое поле pri_remark, которое служит основой для единственного открытого свойства remark, предназначенного только для чтения. В этом свойстве хранится описание, связанное с атрибутом. В474Часть I.
Язык С#классе RemarkAttribute определен один открытый конструктор, который принимаетстроковый аргумент и присваивает его значение полю p r i r e m a r k . Этим, собственно,и ограничиваются функции атрибута RemarkAttribute, который совершенно готов кприменению.Присоединение атрибутаОпределив класс атрибута, можно присоединить его к соответствующему элементу.Атрибут предшествует элементу, к которому он присоединяется, и задается путем заключения его конструктора в квадратные скобки. Например, вот как атрибутRemarkAttribute можно связать с классом:[RemarkAttribute("Этот класс использует атрибут.")]class UseAttrib {При выполнении этого фрагмента кода создается объект класса RemarkAttribute,который содержит комментарий "Этот класс использует атрибут.". Затем атрибут связывается с классом UseAttrib.При связывании атрибута необязательно указывать суффикс A t t r i b u t e .
Например, предыдущее объявление класса можно было бы записать так:[Remark("Этот класс использует атрибут.")]class UseAttrib {Здесь используется только имя Remark. Несмотря на корректность использования этойкороткой формы, при связывании атрибута все же безопаснее использовать его полноеимя, поскольку это позволяет избежать возможной путаницы и неоднозначности.Получение атрибутов объектаПосле того как атрибут присоединен к элементу, другие части программы могутего извлечь. Для этого обычно используют один из двух методов. Первый — методGetCustomAttributes ( ) , который определен в классе Memberlnfo и унаследованклассом Туре. Он считывает список всех атрибутов, связанных с элементом, а форматего вызова таков:object[] GetCustomAttributes(bool searchBases)Если аргумент searchBases имеет значение t r u e , в результирующий списоквключаются атрибуты всех базовых классов по цепочке наследования.