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

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

< Лекция 9 || Лекция 10: 12345 || Лекция 11 >
Аннотация: В язык С# все в большей степени встраиваются декларативные элементы, позволяющие описать, что надо делать, не указывая, как это делается. В данной лекции подробно обсуждаются атрибуты – встроенные и создаваемые программистом, относящиеся к декларативным элементам – метаинформации, сопровождающей проект. Еще одна тема этой лекции – это самодокументирование кода и использование документируемых комментариев. Лекция сопровождается задачами.

Проект к данной лекции Вы можете скачать здесь.

Атрибуты

Атрибуты многократно упоминались в тексте этого курса. Всюду, где речь шла об объявлении сущностей, говорилось, что объявление может сопровождаться указанием атрибутов этой сущности. Но всякий раз говорилось, что атрибуты необязательны, и можно их не задавать. Пришла пора рассмотреть, что представляют собой атрибуты, где и когда их следует использовать. Атрибуты формально не относятся к программному коду. Это декларативная информация, некоторое описание, сопровождающее код. Информация, заданная атрибутом, преобразуется компилятором в метаинформацию, сопровождающую сборку. Метаинформация доступна исполняемой программе благодаря процессу, называемому отражением. В библиотеке CLR есть классы, позволяющие извлечь метаинформацию и анализировать ее. Результаты анализа, естественно, могут влиять на дальнейший ход выполнения программы.

Как и полагается в объектном программировании, атрибуты - это настоящие объекты, принадлежащие различным атрибутным классам. Прародителем всех атрибутных классов является класс с именем Attribute. Все атрибутные классы из Framework .Net являются потомками этого класса, и их имена заканчиваются суффиксом Attribute, который, впрочем, можно опускать при работе с атрибутами. Когда программист создает собственные атрибуты, то он, естественно, определяет собственный атрибутный класс, который также должен быть потомком прародителя атрибутов - класса Attribute и, для следования правилам хорошего тона оформления кода, иметь соответствующий суффикс в имени.

Где могут появляться атрибуты? Атрибуты могут задаваться для самых разных сущностей - классов, структур, интерфейсов, перечислений, делегатов, событий, полей и методов класса, параметров метода и возвращаемых значений. Атрибуты могут быть также заданы для всей сборки и модуля. Атрибуты задаются в точке объявления сущности. Синтаксически атрибут, связываемый с сущностью, заключается в квадратные скобки, непосредственно предшествуя описанию сущности. С одной сущностью могут быть связаны несколько атрибутов. О разных синтаксических формах задания атрибутов поговорим чуть позже, а сейчас рассмотрим первый пример использования атрибутов.

Перечисления и атрибут Flags

В лекции, посвященной перечислениям, подробно рассматривался частный, но важный случай перечислений, представляющий собой шкалы - строки битов. Для работы с перечислениями создан атрибутный класс FlagsAttribute. Появление этого атрибута у перечисления указывает компилятору, что перечисление задает шкалу. Как следствие, изменяется семантика работы методов ToString и Format для перечислений. Метод ToString для объекта перечисления, задающего комбинацию битов, возвращает в качестве результата число, если атрибут Flags не задан, и возвращает совокупность значений, заданных перечислением, если атрибут определен. Аналогичным образом меняется семантика метода Format.

Вернемся к примеру из "Перечисления" , где речь шла о фирме, принимающей программистов на работу. В этом примере был создан специальный класс Job для работы с кандидатами, претендующими на занятие вакансий, описание которого теперь приводить не буду. Свойства кандидатов задавались перечислением. Добавим теперь к этому перечислению атрибут Flags:

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

Здесь приведены три формы записи атрибута. Синтаксически они разнятся, но по сути эквивалентны. Наиболее употребительной является первая краткая форма записи атрибута, где указывается имя класса с опущенным суффиксом. Во второй форме указывается полное имя класса. Третья - полная форма записи - показывает, что фактически вызывается конструктор класса, создающий объект. В случае вызова конструктора без параметров скобки можно опускать так же, как и суффикс в имени класса. Задавать многократно атрибут Flags для одной и той же сущности нельзя, так что две формы из трех закомментированы.

В общем случае атрибутный класс имеет параметры. В данном конкретном случае у атрибутного класса Flags нет параметров, поскольку важен лишь сам факт задания атрибута. Если атрибут задан, то компилятор при работе с перечислением выбирает одну реализацию методов ToString и Format, не задан - другую.

Рассмотрим знакомый по "Перечисления" тест для работы с перечислением:

/// <summary>
  /// Тестирование процесса приема на работу 
  /// </summary>
        public void TestJob()
        {
            Prog_Properties pattern = Prog_Properties.C_sharp |
                Prog_Properties.Web;
            Console.WriteLine("Требования, заданные образцом:" +
                " Знание языка С# и Web-технологии");

            int n = 10;
            Job mys = new Job(n);
            mys.FormCands();
            Prog_Properties[] cand = mys.GetCands();
            //string[] strCand = mys.GetStrCands();
            for (int i = 0; i < n; i++)
            {
                Console.WriteLine("Свойства кандидата[{0}] - {1}",
                    i, cand[i].ToString());
                //Console.WriteLine(strCand[i]);
            }           
        }

Обратите внимание на закомментированные строки:

string[] strCand = mys.GetStrCands();
   Console.WriteLine(strCand[i]);

Ранее, когда атрибут Flags не задавался, я сам создавал реализацию метода ToString, учитывающую особенности работы со шкалами. Теперь при наличии атрибута Flags это становится излишним. Вот как выглядят результаты работы теста.

Перечисления и атрибут Flags

Рис. 9.1. Перечисления и атрибут Flags

Итак, рассмотрен пример простого атрибутного класса. Атрибут Flags может быть задан только для классов, более того - для классов, задающих перечисления, более того - для перечислений, задающих шкалы. Конечно же, есть атрибуты, имеющие более широкое применение.

Класс Attribute

Рассмотрим, как устроен класс Attribute, потомками которого являются все атрибутные классы. Вот описание этого класса:

[SerializableAttribute]
[ClassInterfaceAttribute(ClassInterfaceType.None)]
[ComVisibleAttribute(true)]
[AttributeUsageAttribute(AttributeTargets.All, Inherited = true, AllowMultiple = false)]
public abstract class Attribute : _Attribute

Класс Attribute является абстрактным классом, наследником интерфейса _Attribute. Обратите внимание: атрибуты задаются и при объявлении класса Attribute, так же как и для любого другого класса. Как и положено, класс наследует все методы родительского класса object и определяет методы, заданные интерфейсом _Attribute. У класса определен конструктор без аргументов, ряд статических и динамических (экземплярных) методов. Есть у класса и одно-единственное свойство. Рассмотрим подробнее некоторые из методов класса.

Статические методы: GetCustomAttribute, GetCustomAttributes, IsDefined

Все рассматриваемые в этом разделе методы перегружены. Вот одна из реализаций метода GetCustomAttribute:

public static Attribute GetCustomAttribute(
   MemberInfo element,   Type attributeType)

Метод возвращает ссылку на единственный атрибут, если таковой будет найден, и null - в противном случае. Если находится несколько атрибутов, удовлетворяющих условию поиска, то выбрасывается исключение. Так что этот метод применяется обычно к атрибутам, которые не допускают дублирования, то есть таких, для которых свойство AllowMultiple имеет значение false. Тип первого аргумента задается классом отражения из пространства System.Reflections. Первый аргумент задает элемент, с которым связан атрибут. Второй аргумент задает тип разыскиваемого атрибута. В данной реализации разыскиваются атрибуты, связанные с одним из членов класса - методом, полем, событием. У других реализаций этого метода первый аргумент может задавать другие классы отражения - сборку, класс и другие сущности, с которыми может связываться атрибут. В реализациях возможны и дополнительные аргументы, с чем встретимся в примерах.

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

Метод IsDefined работает более быстро, чем рассмотренные только что методы, он возвращает true, если искомый атрибут есть у элемента, и false - в противном случае.

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

Рассмотрим, как, используя процесс отражения, получить атрибуты, заданные для некоторой сущности. Для определенности рассмотрим класс перечисления Prog_Properties, для которого задан атрибут Flags, и попробуем извлечь этот атрибут из объявления класса. Заодно продемонстрируем два способа получения атрибутов сущности.

/// <summary>
    /// Извлечение атрибутов класса
    /// </summary>
    public void TestTwoMeans()
    {
        Type clstype = typeof(Prog_Properties);
        // Получить атрибуты перечисления,
        //используя класс Attribute
        Console.WriteLine("Класс Attribute!");
        if (Attribute.IsDefined(clstype, 
                typeof(FlagsAttribute), false))
        {
            Attribute att = Attribute.GetCustomAttribute(clstype,
            typeof(FlagsAttribute));
            Console.WriteLine(
                "У класса Prog_Properties есть атрибут {0}", att);
        }                   
        else
          Console.WriteLine("У класса Prog_Properties нет атрибута Flags");

        Attribute[] attrs = Attribute.GetCustomAttributes(
            clstype, false);
        foreach (Attribute attr in attrs)        
            Console.WriteLine(attr);
  
        // Получить атрибуты перечисления,
        //используя класс Type
        Console.WriteLine("Класс Type!");
        if(clstype.IsDefined(typeof(FlagsAttribute), false))
          Console.WriteLine("У класса Prog_Properties есть атрибут Flags");
        else
          Console.WriteLine("У класса Prog_Properties нет атрибута Flags");
      
        object[] attrsob = clstype.GetCustomAttributes(false);
        foreach (Attribute attr in attrsob)       
            Console.WriteLine(attr);
    }

Первым делом создается объект с именем clstype класса Type, содержащий тип анализируемого класса Prog_Properties. Далее вызов метода IsDefined позволяет определить, есть ли у класса атрибут Flags. Если таковой имеется, то вызывается статический метод GetCustomAttribute класса Attribute, которому передаются три аргумента - созданный объект clstype, тип разыскиваемого атрибута и булевский аргумент со значением false, указывающий, что не следует учитывать наследование атрибута. Метод возвращает в качестве результата объект класса FlagsAttribute.

Следующая задача состоит в том, чтобы определить полный список всех атрибутов, связанных с классом. Метод GetCustomAttributes класса Attribute прекрасно решает эту задачу, возвращая в качестве результата массив атрибутов, возможно, пустой. У метода на один аргумент меньше, поскольку ему не нужно передавать тип разыскиваемого атрибута.

Так же просто решаются эти задачи, если использовать работу с методами класса Type, что демонстрируется во второй части нашего примера. Отличия есть, но они незначительны. У класса Type нет метода GetCustomAttribute, так что получать одиночный атрибут сложнее. Второе отличие состоит в том, что метод GetCustomAttributes возвращает массив объектов класса object, а не Attribute, как в первом случае. На рис. 9.2 показаны результаты работы тестового примера.

Получение атрибутов класса

Рис. 9.2. Получение атрибутов класса
< Лекция 9 || Лекция 10: 12345 || Лекция 11 >
Илья Ардов
Илья Ардов

Добрый день!

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

Дарья Федотова
Дарья Федотова
Василий Васенев
Василий Васенев
Россия, город Самара