Атрибуты, сборки, рефлексия
Атрибуты
Атрибут – средство добавления ДЕКЛАРАТИВНОЙ информации к элементам программного кода. Назначение атрибутов – внесение всевозможных не предусмотренных обычным ходом выполнения приложения изменений:
- описание взаимодействия между модулями;
- дополнительная информация, используемая при работе с данными (управление сериализацией);
- отладка;
- и многое другое.
Эта декларативная информация составляет часть метаданных кода. Она может быть использована при помощи механизмов отражения.
Структура атрибута регламентирована. Атрибут – это класс. Общий предок всех атрибутов – класс System.Attribute.
Информация, закодированная с использованием атрибутов, становится доступной в процессе ОТРАЖЕНИЯ (рефлексии типов).
Атрибуты типизированы.
.NET способна прочитать информацию в атрибутах и использовать ее в соответствии с предопределенными правилами или замыслами разработчика. Различаются:
- предопределенные атрибуты. В .NET реализовано множество атрибутов с предопределенными значениями:
DllImport – для загрузки .dll-файлов;
Serializable – означает возможность сериализации свойств объекта – представителя класса;
NonSerialized – обозначает данные-члены класса как несериализуемые. Карандаши (средство графического представления информации, элемент GDI+) не сериализуются;
- производные (пользовательские) атрибуты могут определяться и использоваться в соответствии с замыслами разработчика. Возможно создание собственных (пользовательских) атрибутов. Главные условия:
- соблюдение синтаксиса;
- соблюдение принципа наследования.
В основе пользовательских атрибутов – все та же система типов с наследованием от базового класса System.Attribute.
И неспроста! В конце концов, информация, содержащаяся в атрибутах, предназначается для CLR, и она в случае необходимости должна суметь разобраться в этой информации. Пользователи или другие инструментальные средства должны уметь кодировать и декодировать эту информацию.
Добавлять атрибуты можно к:
- сборкам;
- классам;
- элементам класса;
- структурам;
- элементам структур;
- параметрам;
- возвращаемым значениям.
Ниже приводится описание членов класса Attribute.
TypeId | При реализации в производном классе получает уникальный идентификатор для этого атрибута Attribute |
Finalize (унаследовано от Object ) | Переопределен. Позволяет объекту Object попытаться освободить ресурсы и выполнить другие завершающие операции, перед тем как объект Object будет уничтожен в процессе сборки мусора. В C# для функций финализации используется синтаксис деструктора |
MemberwiseClone (унаследовано от Object ) | Создает неполную копию текущего Object |
Следующий пример является демонстрацией объявления и применения производных атрибутов:
using System; using System.Reflection; namespace CustomAttrCS { // Перечисление of animals. // Start at 1 (0 = uninitialized). public enum Animal { // Pets. Dog = 1, Cat, Bool, } // Перечисление of colors. // Start at 1 (0 = uninitialized). public enum Color { // Colors. Red = 1, Brown, White, } // Класс пользовательских атрибутов. public class AnimalTypeAttribute : Attribute {//============================================================== // Данное - член типа "перечисление". protected Animal thePet; protected string WhoIs(Animal keyPet) { string retStr = ""; switch (keyPet) { case Animal.Dog: retStr = "This is the Dog!"; break; case Animal.Cat: retStr = "This is the Cat!"; break; case Animal.Bull: retStr = "This is the Bool!"; break; default: retStr = "Unknown animal!"; break; } return retStr; } // Конструктор вызывается при установке атрибута. public AnimalTypeAttribute(Animal pet) { thePet = pet; Console.WriteLine("{0}", WhoIs(pet)); } // Свойство, демонстрирующее значение атрибута. public Animal Pet { get { return thePet; } set { thePet = value; } } }//============================================================== // Еще один класс пользовательских атрибутов. public class ColorTypeAttribute : Attribute {//============================================================== // Данное - член типа "перечисление". protected Color theColor; // Конструктор вызывается при установке атрибута. public ColorTypeAttribute(Color keyColor) { theColor = keyColor; } // Свойство, демонстрирующее значение атрибута. public Color ColorIs { get { return theColor; } set { theColor = ColorIs; } } }//============================================================== // A test class where each method has its own pet. class AnimalTypeTestClass {//============================================================== // Содержит объявления трех методов, каждый из которых // предваряется соответствующим ПОЛЬЗОВАТЕЛЬСКИМ атрибутом. // У метода может быть не более одного атрибута данного типа. [AnimalType(Animal.Dog)] [ColorType(Color.Brown)] public void DogMethod() { Console.WriteLine("This is DogMethod()..."); } [AnimalType(Animal.Cat)] public void CatMethod() { Console.WriteLine("This is CatMethod()..."); } [AnimalType(Animal.Bool)] [ColorType(Color.Red)] public void BoolMethod(int n, string voice) { int i; Console.WriteLine("This is BoolMethod!"); if (n > 0) for (i = 0; i < n; i++) { Console.WriteLine(voice); } } }//============================================================== class DemoClass {//============================================================== static void Main(string[] args) { int invokeFlag; int i; // И вот ради чего вся эта накрутка производилась... // Объект класса AnimalTypeTestClass под именем testClass // представляет собой КОЛЛЕКЦИЮ методов, каждый из которых // снабжен соответствующим ранее определенным пользовательским // СТАНДАРТНЫМ атрибутом. У класса атрибута AnimalTypeAttribute есть все, // что положено иметь классу, включая конструктор. AnimalTypeTestClass testClass = new AnimalTypeTestClass(); // Так вот создали соответствующий объект - представитель класса... // Объект - представитель класса сам может служить источником // информации о собственном классе. Информация о классе представляется // методом GetType() в виде // объекта - представителя класса Type. Информационная капсула! Type type = testClass.GetType(); // Из этой капсулы можно извлечь множество всякой "полезной" информации... // Например, можно получить коллекцию (массив) элементов типа MethodInfo // (описателей методов), которая содержит список описателей методов, // объявленных в данном классе. В список будет включена информация // о ВСЕХ методах класса: о тех, которые были определены явным // образом, и о тех, которые были унаследованы // от базовых классов. И по этому списку описателей методов мы // пройдем победным маршем ("Ha-Ha-Ha") оператором foreach. i = 0; foreach(MethodInfo mInfo in type.GetMethods()) { invokeFlag = 0; Console.WriteLine("#####{0}#####{1}#####", i, mInfo.Name); // И у каждого из методов мы спросим относительно множества атрибутов, // которыми метод был снабжен при объявлении класса. foreach (Attribute attr in Attribute.GetCustomAttributes(mInfo)) { Console.WriteLine("~~~~~~~~~~"); // Check for the AnimalType attribute. if (attr.GetType() == typeof(AnimalTypeAttribute)) { Console.WriteLine("Method {0} has a pet {1} attribute.", mInfo.Name, ((AnimalTypeAttribute)attr).Pet); // Посмотрели значение атрибута – и если это Animal.Bool – подняли флажок. if (((AnimalTypeAttribute)attr).Pet.CompareTo(Animal.Bool) == 0) invokeFlag++; } if (attr.GetType() == typeof(ColorTypeAttribute)) { Console.WriteLine("Method {0} has a color {1} attribute.", mInfo.Name, ((ColorTypeAttribute)attr).ColorIs); // Посмотрели значение атрибута – и если это Color.Red – // подняли флажок второй раз. if (((ColorTypeAttribute)attr).ColorIs.CompareTo(Color.Red) == 0) invokeFlag++; } // И если случилось счастливое совпадение значений атрибутов метода // (Красный Бычок), то метод выполняется. // Метод Invoke в варианте с двумя параметрами: // объект - представитель исследуемого класса // (в данном случае AnimalTypeTestClass), и массив объектов-параметров. if (invokeFlag == 2) { object[] param = {5,"Mmmuuu–uu–uu!!! Mmm..."}; mInfo.Invoke(new AnimalTypeTestClass(),param); } Console.WriteLine("~~~~~~~~~~"); } Console.WriteLine("#####{0}#####", i); i++; } } }//============================================================== }Листинг 10.2.
Эта программа демонстрирует одну замечательную особенность синтаксиса объявления атрибутов. При их применении можно использовать сокращенные имена ранее объявленных классов атрибутов.
В программе объявлено два класса атрибутов – наследников класса Attribute:
public class AnimalTypeAttribute : Attribute... public class ColorTypeAttribute : Attribute...
При объявлении соответствующих экземпляров атрибутов последняя часть имени класса атрибута ( ...Attribute ) была опущена.
Вместо
[AnimalTypeAttribute(Animal.Dog)]
используется имя
[AnimalType(Animal.Dog)],
вместо
[ColorTypeAttribute(Color.Red)]
используется
[ColorType(Color.Red)]
Транслятор терпимо относится только к этой модификации имени класса атрибута. Любые другие изменения имен атрибутов пресекаются.