Атрибуты, сборки, рефлексия
Атрибуты
Атрибут – средство добавления ДЕКЛАРАТИВНОЙ информации к элементам программного кода. Назначение атрибутов – внесение всевозможных не предусмотренных обычным ходом выполнения приложения изменений:
- описание взаимодействия между модулями;
- дополнительная информация, используемая при работе с данными (управление сериализацией);
- отладка;
- и многое другое.
Эта декларативная информация составляет часть метаданных кода. Она может быть использована при помощи механизмов отражения.
Структура атрибута регламентирована. Атрибут – это класс. Общий предок всех атрибутов – класс 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)]
Транслятор терпимо относится только к этой модификации имени класса атрибута. Любые другие изменения имен атрибутов пресекаются.