1629295407-c61bfe4caba98380ea3e7cdae6295416 (846200), страница 86
Текст из файла (страница 86)
Значение x: 1, значение y: 23Вызов метода showЗначение x: 1, значение y: 23Обратите внимание вот на что. Во-первых, программа получает (и использует)информацию только о тех методах, которые явно объявлены в классе MyClass. Этафильтрация достигается благодаря использованию BindingFlags-формата вызова методаGetMethods(). Тем самым становится возможным отсев унаследованных методов. Вовторых, отметьте, каким образом программа динамически получает количество параметрови тип значений, возвращаемых каждым методом.
Количество параметров определяется спомощью switch-инструкции. В каждой case-ветви этой инструкции проверяется тип(типы) параметра (параметров) и тип возвращаемого методом значения. Затем на основеэтой информации и организуется соответствующий вызов метода.Глава 17. Динамическая идентификация типов, отражение и атрибуты473АтрибутыВ C# предусмотрена возможность вносить в программу информацию описательногохарактера в формате атрибута.
Атрибут содержит дополнительные сведения о классе,структуре, методе и т.д. Например, можно создать атрибут, определяющий тип кнопки, дляотображения которой предназначен класс. Атрибуты указываются внутри квадратныхскобок, предваряя элемент, к которому они применяются. Таким образом, атрибут неявляется членом класса.
Он просто содержит дополнительную информацию об элементе.Основы применения атрибутовАтрибут поддерживается классом, производным от класса System.Attribute.Таким образом, все классы атрибутов являются подклассами класса Attribute. Хотякласс Attribute определяет фундаментальную функциональность, она не всегдавостребована при работе с атрибутами. Для классов атрибутов принято использоватьсуффикс Attribute.
Например, для класса атрибута, предназначенного для описанияошибок, вполне подошло бы имя ErrorAttribute.Объявление класса атрибута предваряется атрибутом AttributeUsage. Этотвстроенный атрибут задает типы элементов, к которым может применяться объявляемыйатрибут.Создание атрибутаВ классе атрибута определяются члены, которые поддерживают данный атрибут.Обычно классы атрибутов очень просты и содержат лишь небольшое количество полей илисвойств. Например, в атрибуте может содержаться комментарий, описывающий элемент, ккоторому относится атрибут. Такой атрибут может иметь следующий вид:[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.
Его объявление предваряется атрибутомAttributeUsage, который означает, что атрибут RemarkAttribute можно применитьк элементам всех типов. С помощью атрибута AttributeUsage можно сократить списокэлементов, к которым будет относиться атрибут; эту возможность мы рассмотрим вследующей главе.Затем следует объявление класса атрибута RemarkAttribute, производного отAttribute. Класс RemarkAttribute содержит одно закрытое поле pri_remark,которое служит основой для единственного открытого свойства remark, предназначенноготолько для чтения. В этом свойстве хранится описание, связанное с атрибутом.
В474Часть I. Язык C#классе RemarkAttribute определен один открытый конструктор, который принимаетстроковый аргумент и присваивает его значение полю pri_remark. Этим, собственно, иограничиваются функции атрибута RemarkAttribute, который совершенно готов кприменению.Присоединение атрибутаОпределив класс атрибута, можно присоединить его к соответствующему элементу.Атрибут предшествует элементу, к которому он присоединяется, и задается путемзаключения его конструктора в квадратные скобки. Например, вот как атрибутRemarkAttribute можно связать с классом:[RemarkAttribute("Этот класс использует атрибут.")]class UseAttrib {//...}При выполнении этого фрагмента кода создается объект класса RemarkAttribute,который содержит комментарий “Этот класс использует атрибут.”.
Затем атрибутсвязывается с классом UseAttrib.При связывании атрибута необязательно указывать суффикс Attribute. Например,предыдущее объявление класса можно было бы записать так:[Remark("Этот класс использует атрибут.")]class UseAttrib {// ...}Здесь используется только имя Remark. Несмотря на корректность использованияэтой короткой формы, при связывании атрибута все же безопаснее использовать его полноеимя, поскольку это позволяет избежать возможной путаницы и неоднозначности.Получение атрибутов объектаПосле того как атрибут присоединен к элементу, другие части программы могут егоизвлечь. Для этого обычно используют один из двух методов.
Первый — методGetCustomAttributes(), который определен в классе MemberInfo и унаследованклассом Туре. Он считывает список всех атрибутов, связанных с элементом, а формат еговызова таков:object[] GetCustomAttributes(bool searchBases)Если аргумент searchBases имеет значение true, в результирующий списоквключаются атрибуты всех базовых классов по цепочке наследования. В противном случаебудут включены только атрибуты, определенные заданным типом.Второй — метод GetCustomAttribute(), который определен в классеAttribute.
Вот один из форматов его вызова:static Attribute GetCustomAttribute(MemberInfo mi,Type attribtype)Здесь аргумент mi означает объект класса MemberInfo, описывающий элемент, длякоторого извлекается атрибут. Нужный атрибут указывается аргументом attribtype.Этот метод используется в том случае, если известно имя атрибута, который нужнополучить.
Например, чтобы получить ссылку на атрибут RemarkAttribute, можноиспользовать такую последовательность:// Считываем RemarkAttribute.Type tRemAtt = typeof(RemarkAttribute);RemarkAttribute ra = (RemarkAttribute)Attribute.GetCustomAttribute(t, tRemAtt);Глава 17. Динамическая идентификация типов, отражение и атрибуты475Имея ссылку на атрибут, можно получить доступ к его членам.
Другими словами,информация, связанная с атрибутом, доступна программе, использующей элемент, ккоторому присоединен атрибут. Например, при выполнении следующей инструкцииотображается значение поля remark:Console.WriteLine(ra.remark);Использование атрибута RemarkAttribute демонстрируется в приведенной нижепрограмме.// Простой пример атрибута.using System;using System.Reflection;[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("Этот класс использует атрибут.")]class UseAttrib {// ...}class AttribDemo {public static void Main() {Type t = typeof(UseAttrib);Console.Write("Атрибуты в " + t.Name + ": ");object[] attribs = t.GetCustomAttributes(false);foreach(object o in attribs) {Console.WriteLine(o);}Console.Write("Remark: ");}}// Считываем атрибут RemarkAttribute.Type tRemAtt = typeof(RemarkAttribute);RemarkAttribute ra = (RemarkAttribute)Attribute.GetCustomAttribute(t, tRemAtt);Console.WriteLine(ra.remark);Результаты выполнения этой программы таковы:Атрибуты в UseAttrib: RemarkAttributeRemark: Этот класс использует атрибут.476Часть I.
Язык C#Сравнение позиционных и именованных параметровВ предыдущем примере атрибут RemarkAttribute был инициализированпосредством передачи конструктору строки описания. В этом случае, т.е. прииспользовании обычного синтаксиса конструктора, параметр comment, принимаемыйконструкторомRemarkAttribute(),называетсяпозиционнымпараметром.Возникновение этого термина объясняется тем, что аргумент метода связывается спараметром посредством своей позиции. Так работают в C# все методы и конструкторы. Нодля атрибутов можно создавать именованные параметры и присваивать им начальныезначения, используя их имена.Именованный параметр поддерживается либо открытым полем, либо свойством,которое не должно быть предназначено только для чтения. Такое поле или свойствоавтоматически можно использовать в качестве именованного параметра.
При определенииатрибута для элемента именованный параметр получает значение с помощью инструкцииприсваивания, которая содержится в конструкторе атрибута. Формат спецификацииатрибута, включающей именованные параметры, таков:[attrib(список_позиционных_параметров,именованный_параметр_1 = value,именованиий_параметр_2 = value, ...)]Позиционные параметры (если они имеются) должны стоять в начале спискапараметров.
Затем каждому именованному параметру присваивается значение. Порядокследования именованных параметров не имеет значения. Именованным параметрамприсваивать значения необязательно. Но в данном случае значения инициализациииспользованы по назначению.Чтобы понять, как используются именованные параметры, лучше всего рассмотретьпример. Перед вами версия определения класса RemarkAttribute, в которую добавленополе supplement, позволяющее хранить дополнительный комментарий.[AttributeUsage(AttributeTargets.All)]public class RemarkAttribute : Attribute {string pri_remark; // Вазовое поле для свойства remark.public string supplement; // Это именованный параметр.public RemarkAttribute(string comment) {pri_remark = comment;supplement = "Данные отсутствуют";}}public string remark {get {return pri_remark;}}Как видите, поле supplement инициализируется строкой “Данные отсутствуют” вконструкторе класса.
С помощью конструктора невозможно присваивать этому полюдругое начальное значение. Однако, как показано в следующем фрагменте кода, полеsupplement можно использовать в качестве именованного параметра:[RemarkAttribute("Этот класс использует атрибут.",supplement = "Это дополнительная информация.")]class UseAttrib {// ...}Глава 17. Динамическая идентификация типов, отражение и атрибуты477Обратите особое внимание на то, как вызывается конструктор классаRemarkAttribute.
Сначала задается позиционный аргумент. За ним, после запятой,следует именованный параметр supplement, которому присваивается значение.Обращение к конструктору завершает закрывающая круглая скобка. Таким образом,именованный параметр инициализируется внутри вызова конструктора.
Этот синтаксисможно обобщить. Итак, позиционные параметры необходимо задавать в порядке, которыйопределен конструктором. Именованные параметры задаются посредством присваиваниязначений их именам.Рассмотрим программу, которая демонстрирует использование поля supplement:// Использование именованного параметра атрибута.using System;using System.Reflection;[AttributeUsage(AttributeTargets.All)]public class RemarkAttribute : Attribute {string pri_remark; // Базовое поле для свойства remark.public string supplement; // Это именованный параметр.public RemarkAttribute(string comment) {pri_remark = comment;supplement = "Данные отсутствуют";}}public string remark {get {return pri_remark;}}[RemarkAttribute("Этот класс использует атрибут.",supplement = "Это дополнительная информация.")]class UseAttrib {// ...}class NamedParamDemo {public static void Main() {Type t = typeof(UseAttrib);Console.Write("Атрибуты в " + t.Name + ": ");object[] attribs = t.GetCustomAttributes(false);foreach(object o in attribs) {Console.WriteLine(o);}// Считывание атрибута RemarkAttribute.Type tRemAtt = typeof(RemarkAttribute);RemarkAttribute ra = (RemarkAttribute)Attribute.GetCustomAttribute(t, tRemAtt);Console.Write("Remark: ");Console.WriteLine(ra.remark);478Часть I.
Язык C#}}Console.Write("Supplement: ");Console.WriteLine(ra.supplement);Вот результаты выполнения этой программы:Атрибуты в UseAttrib: RemarkAttributeRemark: Этот класс использует атрибут.Supplement: Это дополнительная информация.Как разъяснялось выше, в качестве именованного параметра можно такжеиспользовать свойство.