Здравствуйте! Записался на ваш курс, но не понимаю как произвести оплату. Надо ли писать заявление и, если да, то куда отправлять? как я получу диплом о профессиональной переподготовке? |
Декларативность. Атрибуты и теги
Проект к данной лекции Вы можете скачать здесь.
Атрибуты
Атрибуты многократно упоминались в тексте этого курса. Всюду, где речь шла об объявлении сущностей, говорилось, что объявление может сопровождаться указанием атрибутов этой сущности. Но всякий раз говорилось, что атрибуты необязательны, и можно их не задавать. Пришла пора рассмотреть, что представляют собой атрибуты, где и когда их следует использовать. Атрибуты формально не относятся к программному коду. Это декларативная информация, некоторое описание, сопровождающее код. Информация, заданная атрибутом, преобразуется компилятором в метаинформацию, сопровождающую сборку. Метаинформация доступна исполняемой программе благодаря процессу, называемому отражением. В библиотеке 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 может быть задан только для классов, более того - для классов, задающих перечисления, более того - для перечислений, задающих шкалы. Конечно же, есть атрибуты, имеющие более широкое применение.
Класс 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 показаны результаты работы тестового примера.