Тверской государственный университет
Опубликован: 02.12.2009 | Доступ: свободный | Студентов: 2375 / 262 | Оценка: 4.47 / 4.24 | Длительность: 14:45:00
Лекция 10:

Декларативность. Атрибуты и теги

< Лекция 9 || Лекция 10: 12345 || Лекция 11 >

Атрибутные классы

Рассмотрим, как устроены атрибутные классы - наследники класса Attribute. Эти классы имеют специфику, отличающую их от других классов. У них, помимо наследуемых от родителя, могут быть заданы свои поля и свойства. Атрибутные классы могут переопределять методы родителя.

Часть полей класса называются позиционными параметрами класса. К ним относят параметры, обязательные для задания. Фактические значения этих параметров всегда передаются конструктору класса в момент задания атрибута. Эти поля могут быть закрытыми.

Другие необязательные поля или свойства атрибутного класса называются именованными. Они должны быть не статическими, открытыми и допускать как чтение, так и запись. Если при объявлении атрибута требуется задать значение именованного параметра, то это значение передается также конструктору в виде пары < имя = значение>. Конструктору вначале передаются позиционные параметры в определенном порядке, а затем именованные параметры, порядок следования которых может быть произвольным. Наличие позиционных и именованных параметров и способ их передачи конструктору является синтаксическим отличием атрибутных классов.

Атрибутный класс AttributeUsageAttribute

Атрибуты этого класса задаются для большинства атрибутных классов. Будучи заданным для атрибутного класса, атрибут определяет, как могут использоваться атрибуты определяемого класса. Если взглянуть на приведенное ранее определение класса Attribute, то и для него задан атрибут AttributeUsage. Этот атрибут предваряет и определение самого класса AttributeUsageAttribute.

У класса AttributeUsage три параметра.

  • ValidOn - позиционный параметр. Его значения задаются перечислением AttributeTargets, представляющим шкалу. Значение может быть составным и задаваться с использованием побитовой логической операции < | >. Значение параметра определяет, для каких элементов программы может быть задан атрибут определяемого класса. Для класса Attribute значение этого параметра равно AttributeTargets.All, указывающее, что атрибуты могут появляться для всех возможных элементов. Для класса FlagsAttribute значение этого параметра равно AttributeTargets.Enum, указывающее, что атрибут, задающий флаги, может использоваться только для перечислений.
  • Inherited - именованный параметр. Его значения true или false определяют, может ли атрибутный класс быть наследуемым.
  • AllowMultiple - именованный параметр. Его значения true или false определяют, может ли с программным элементом связываться несколько экземпляров атрибута или он может быть задан только в одном экземпляре.

Приведу примеры задания этого атрибута соответственно для классов Attribute, AttributeUsage и Flags:

  • [AttributeUsageAttribute(AttributeTargets.All, Inherited = true, AllowMultiple = false)]
  • [AttributeUsageAttribute(AttributeTargets.Class, Inherited = true)]
  • [AttributeUsageAttribute(AttributeTargets.Enum, Inherited = false)]

Связывание атрибута с программным элементом

Как уже говорилось, в момент объявления сущности - элемента программы - можно задать атрибуты, связывая их с программным элементом. Синтаксическим признаком атрибута являются квадратные скобки, текст внутри которых можно рассматривать как вызов конструктора, создающего объект соответствующего атрибутного класса. Для одной сущности можно задать несколько атрибутов, принадлежащих как разным атрибутным классам, так и одному классу, если параметр AllowMultiple этого класса имеет значение true. Связывать атрибут с программным элементом можно только тогда, когда программный элемент соответствует цели атрибута, устанавливаемой параметром ValidOn и задаваемой перечислением AttributeTargets. Примеры подобного объявления атрибутов для программных элементов уже приводились.

Точные правила спецификации атрибутов и связывания их с сущностью следующие. Для связывания сущности с атрибутами можно задать одну или несколько атрибутных секций. Каждая атрибутная секция окаймляется квадратными скобками и содержит один атрибут или список атрибутов, элементы которого разделяются запятыми. Порядок следования атрибутных секций и порядок следования атрибутов в списке не играет значения. Так что спецификации атрибутов [A][B], [B][A], [A, B], и [B, A] эквивалентны.

Рассмотрим некоторые особенности связывания. Атрибуты можно задавать для сущностей, не объявляемых в тексте программы. Такими сущностями являются сборка ( assembly ) и модуль ( module ). Сборка создается для каждого проекта и, помимо программного кода, содержит метаинформацию, описывающую элементы проекта. Атрибуты - это часть метаинформации, содержащейся в манифесте сборки. Когда говорится, что в момент связывания атрибута с программным элементом конструктор атрибутного класса создает объект соответствующего класса, это некоторая метафора, поясняющая семантику. Реально объекты не создаются, а просто в манифест сборки добавляется соответствующая информация о данном программном элементе. Понятно, что манифест сборки в первую очередь описывает саму сборку, в том числе и атрибуты, характеризующие сборку. Как же задать эти атрибуты, ведь в программном тексте нет объявления сборки? Все очень просто - атрибуты, связанные со сборкой или с модулем, задаются в самом начале программного текста любого класса, сразу после предложений using, еще до задания пространства имен. Синтаксически связывание атрибута со сборкой или с модулем достигается за счет того, что конструктору атрибута может предшествовать задание цели, отделяемое от конструктора двоеточием. Вот, например, как можно задать атрибуты для сборки и для модуля при объявлении знакомого нам класса Prog_Properties, который входит в проект ConsoleAttributes, содержащего примеры этой лекции:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

// Установить CLSCompliant атрибут как true
// Этот атрибут применим для сборок  /target:assembly.
//Проверяет соответствие сборки общеязыковым спецификациям
[assembly: CLSCompliant(true)]

// Связывание атрибута Description с модулем проекта.
[module: Description("Описание модуля проекта ConsoleAttributes")]

namespace ConsoleAttributes
{
    /// <summary>
    /// Свойства претендентов на должность программиста,
    /// описывающие знание технологий и языков программирования
    /// </summary>
    
    //[FlagsAttribute]
    //[FlagsAttribute()]
    [Flags]
    public enum Prog_Properties
    {
        VB = 1, C_sharp = 2, C_plus_plus = 4,
        Web = 8, Prog_1C = 16
    }
}

Все атрибуты, связываемые со сборкой и модулем одного проекта и заданные при объявлении разных классов проекта, собираются вместе в манифесте сборки. Уточню, что модулем проекта является переносимый исполняемый файл проекта - PE-файл проекта.

При связывании атрибутов со сборкой и модулем задание цели обязательно, поскольку в программном коде нет объекта, с которым связывается атрибут. Но это не единственный случай, когда задание цели необходимо. Рассмотрим, например, связывание атрибута с методом и значением, возвращаемым этим методом:

[method: Description("Метод, позволяющий получить свойства кандидатов")]
[return: Description("Массив строк со свойствами кандидатов")]
public string[] GetStrCands() {}

Атрибут Description может связываться как с методом, так и с возвращаемым значением. Если он задан в единственном экземпляре и цель не задана, то атрибут по умолчанию связывается с наиболее общим случаем - с методом. Задание цели уточняет, с какой сущностью следует связать атрибут. Заметьте, цель можно задавать всегда, даже когда неоднозначность связывания не возникает.

Как связать атрибуты с модулем и сборкой, понятно. А как решить обратную задачу - выяснить, какие атрибуты связаны со сборкой и модулем? Для этого нужно уметь получать объекты класса Assembly и Module. Покажем, как это делается, извлекая информацию об атрибутах для сборки, модуля, метода и возвращаемого значения. Пример извлечения атрибутов для класса уже появлялся. Расширим его теперь на сущности других типов.

public void TestGetEntityAttributes()
    {
        //Получение атрибутов сборки и модуля
        Type clstype = typeof(Prog_Properties);
        Assembly projectAssembly = clstype.Assembly;
        Module projectModule = clstype.Module;
        Attribute[] assemblyAttributes =
            Attribute.GetCustomAttributes(projectAssembly);
        Console.WriteLine("Атрибуты сборки");
        foreach (Attribute attr in assemblyAttributes)
            Console.WriteLine(attr);

        Attribute[] moduleAttributes =
            Attribute.GetCustomAttributes(projectModule);
        Console.WriteLine("Атрибуты модуля");
        foreach (Attribute attr in moduleAttributes)
            Console.WriteLine(attr);

    }

Заметьте, что существуют классы Assembly и Module. Получить объекты этих классов, связанных с проектом, достаточно просто. Можно создать объект класса Type для одного из классов проекта, а затем использовать свойства Module и Assembly, возвращающие нужные нам объекты. Далее метод GetCustomAttributes, которому передаются в качестве первого аргумента объекты, задающие сборку или модуль, будет возвращать массив связанных с ними атрибутов. Уже отмечалось, что метод перегружен, но у него есть реализации, где первый аргумент принадлежит классам Assembly и Module. На рис. 9.3 показаны результаты работы этого теста.

Атрибуты сборки и модуля

Рис. 9.3. Атрибуты сборки и модуля

Обратите внимание, что у сборки по умолчанию установлено большое число атрибутов. У модуля таких устанавливаемых по умолчанию атрибутов нет, так что в данном случае у модуля появился только тот атрибут, который был установлен нами в тексте программы.

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

//Получение атрибутов метода и возвращаемого значения
        Type typeJob = typeof(Job);
        MethodInfo classMethod = typeJob.GetMethod("GetStrCands");

        Attribute[] methodAttributes =
            Attribute.GetCustomAttributes(classMethod);
        Console.WriteLine("Атрибуты метода");
        foreach (Attribute attr in methodAttributes)
            Console.WriteLine(attr);

        Console.WriteLine("Атрибуты вовращаемого значения");
       ParameterInfo returnMethod = classMethod.ReturnParameter;
       foreach(Attribute attr in returnMethod.GetCustomAttributes(false))
           Console.WriteLine(attr);

Первым делом получен объект typeJob, задающий тип класса. С использованием метода GetMethod получен объект classMethod класса MethodInfo. Этот объект содержит подробную информацию о методе класса. В частности, можно получить атрибуты, связанные с методом. Это можно делать двояким способом, либо вызывая статический метод GetCustomAttributes класса Attribute, либо вызвать динамический метод с тем же именем, определенный в классе MethodInfo.

Созданный объект classMethod позволяет получить объект returnMethod класса ParameterInfo, содержащий информацию о значении, возвращаемом методом. Для получения атрибутов объектов classMethod и returnMethod используется в первом случае статический метод, во втором, для разнообразия, - динамический метод GetCustomAttributes.

Для полноты картины рассмотрим, как получить не только сам атрибут, связанный с сущностью, но и параметры атрибута, если таковые имеются. У атрибута Description, приписанного в нашем примере к методу и к возвращаемому значению, есть параметр, задающий описание сущности. Это описание задавалось в момент объявления атрибута. Теперь постараемся извлечь его:

//Работа с атрибутом Description
  Console.WriteLine("Получение параметров атрибута Description");

  DescriptionAttribute des;
  MemberInfo specMethod = typeJob.GetMethod("GetStrCands");
  if (Attribute.IsDefined(specMethod, typeof(DescriptionAttribute)))
  {
      des = (DescriptionAttribute)Attribute.GetCustomAttribute(specMethod,
         typeof(DescriptionAttribute));
      Console.WriteLine(des.Description);
  }
  ParameterInfo retMethod = classMethod.ReturnParameter;
  des = (DescriptionAttribute)Attribute.GetCustomAttribute(retMethod,
        typeof(DescriptionAttribute));
  Console.WriteLine(des.Description);

Главной особенностью этого фрагмента кода, на которую следует обратить внимание, является то, что для получения конкретного атрибута целесообразно использовать статический метод GetCustomAttribute, который есть только у класса Attribute. Важно также и то, что полученный атрибут нужно привести к заданному типу. Тогда соответствующий объект позволит добраться и до параметров атрибутов. Взгляните на результаты работы двух последних фрагментов кода.

Атрибуты метода и параметры

Рис. 9.4. Атрибуты метода и параметры
< Лекция 9 || Лекция 10: 12345 || Лекция 11 >
Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?

Дарья Федотова
Дарья Федотова
Михаил Алексеев
Михаил Алексеев
Россия, Уфа, УГАТУ, 2002
Олег Корсак
Олег Корсак
Латвия, Рига