Создание атрибута начинается с создания класса, реализующего атрибут. Такой класс должен быть наследуем от любого класса атрибута. Класс атрибута всегда должен иметь модификатор доступа public. Класс атрибута всегда непосредственно или опосредованно наследуется от класса System.Attribute.
Объявление пользовательского атрибута начинается со встроенного атрибута AttributeUsageAttribute, который задает ключевые характеристики нового класса. Например, он определяет, может ли атрибут наследоваться другими классами, или к каким элементам он может применяться. Следующий фрагмент кода иллюстрирует применение атрибута AttributeUsageAttribute
Класс System.AttributeUsageAttribute содержит три члена, которые важны для создания пользовательских атрибутов. Это поля: AttributeTargets, Inherited и AllowMultiple.
Поле AttributeTargets
В предыдущем примере используется флаг AttributeTargets.All. Этот флаг означает, что данный атрибут может применяться к любым элементам программы. С другой стороны, можно задать флаг AttributeTargets.Class, означающий, что атрибут применяется только к классам, или AttributeTargets.Method – для методов классов и интерфейсов. Подобным образом можно применять и пользовательские атрибуты.
Также можно использовать несколько экземпляров атрибута AttributeTargets. В следующем примере показано, как пользовательский атрибут может применяться к любому классу или методу:
Свойство Inherited определяет, будет ли атрибут наследоваться классами, наследниками того, к которому этот атрибут применен. Это свойство может принимать два значения: true или false. По умолчанию Inherited = true.
Свойство AllowMultiple
Это свойство показывает, может ли атрибут применяться многократно к одному элементу. По умолчанию оно равно false, что значит – атрибут может использоваться только один раз.
Ниже приведен пример создания собственного атрибута.
set {kod = value; } // Назначение защищенной переменной
// класса значения параметра
}
//Конструктор атрибута
public TestAttribute(string name)
{ this.name = name;
this.kod = 12345;
}
}
//Конец объявления атрибута
Как видно, атрибут TestAttribute является потомком класса System.Attribute. Перед определением нового атрибута TestAttribute мы видим строку, которая определяет область применения этого атрибута
Далее мы определяем для нашего атрибута внутренние переменные name типа string и kod типа int.
Затем в классе атрибута записано свойство Name только для чтения и свойство Kod для чтения и записи. Свойства определяются, если нужно передавать именованные параметры в конструкторы или легко и удобно получать значения полей атрибута.
Затем определяется конструктор с параметром name типа string. В конструкторе мы записываем значение в поле name.
Конструкторы атрибутивного класса можно перегружать, чтобы принимать различные комбинации параметров. Если для атрибутивного класса определены свойства, в нашем примере Name и Kod, для инициализации конструктора можно использовать комбинацию позиционных и именованных параметров. Обычно все обязательные параметры объявляются как позиционные, а необязательные как именованные.
После создания класса атрибута мы применяем его к другому классу Test. Для этого в коде мы должны создать экземпляр атрибута TestAttribute непосредственно перед классом Test. В нашем случае возможны два варианта задания атрибута – только с позиционным параметром
[TestAttribute("Hallow Word")]
или с позиционным и именованным параметром
[TestAttribute("Hallow Word"), Kod=123]
Для примера используем последний вариант атрибута. В результате получим следующий текст
{ // Вызвать функцию получения и отображения атрибута
GetAttribute(typeof(Test));
}
public static void GetAttribute(Type t)
{ //Получение экземпляра атрибута TestAttribute класса Test
TestAttribute att=
(TestAttribute) Attribute.GetCustomAttribute(t,
typeof(TestAttribute));
//Получение и вывод свойства атрибута TestAttribute
Console.WriteLine("{0}", att.Name);
}
}
}
В классе Test в методе GetAttribute на консоль выводится значение свойства Name этого атрибута. Для этого мы воспользовались статическим методом GetCustomAttribute класса Attribute. Этот метод принимает в качестве параметров тип Test, к которому применяется атрибут, и собственно применяемый атрибут TestAttribute. Метод возвращает экземпляр атрибута, который используется для получения значения свойства Name.
Метод GetAttribute вызываем в статическом методе Main.
Результатом выполнения программы будет вывод на консоль слов "Hallow Word".
Пример декларативного программирования
Рассмотрим этот механизм на примере работы процедуры протоколирования, приведенной в подразделе "Трассировка полей объекта". В своем простейшем виде она выводит абсолютно все поля объекта без разбора. С помощью атрибутов можно создать механизм управления выводом в log тех или иных полей класса (фильтрация). Для этого достаточно ввести новый атрибут, обозначающий, что протоколирование данного поля не требуется. Ниже приведено описание атрибута NotTrace:
[AttributeUsage(AttributeTargets.Field) ]
public class NotTrace : Attribute
{
}
Чтобы упростить использование этого атрибута, напишем простую вспомогательную функцию:
Теперь можно модифицировать функцию протоколирования, чтобы она выводила только те поля, у которых не задан атрибут NotTrace:
foreach (FieldInfo fi in type.GetFields(...))
{ if (! NeedTrace(fi))
continue;
...
}
Теперь управление выводом полей производится не изменением кода функции протоколирования, а изменением описаний полей – заданием их атрибутов. Другими словами, судьба объекта задаётся при его декларации – декларативное программирование.