Содержание
Атрибуты позволяют добавлять пользовательскую информацию к метаданным элементов кода: сборкам, типам, членам, возвращаемым значениям и параметрам. Например, атрибуты могут быть использованы для сериализации — процесс преобразования произвольных объектов в определенный формат. В этом случае атрибуты полей могут точно устанавливать как преобразовывать поля. Можно написать собственный атрибут и затем использовать его для добавления дополнительной информации к элементам кода. Эта дополнительная информация будет скомпилирована в метаданные сборки. Позже она может быть извлечена с помощью рефлексии.
Существует три вида атрибутов:
- атрибуты с побитовым отражением
- специальные атрибуты (пользовательские, custom)
- псевдоспециальные атрибуты
Расширяемыми являются только специальные атрибуты.
Атрибуты с побитовым отображением
Атрибуты с побитовым отображением отображаются на выделенные биты в метаданных типа. Большинство ключевых слов модификаторов (public, abstract, sealed и др.) компилируются в атрибуты с побитовым отображением. Эти атрибут эффективны в использовании, т.к. занимают мало памяти в метаданных (как правило один бит) и находятся средой CLR с незначительными затратами ресурсов.
При рефлексии их можно извлечь через специальные свойства типа Type и подклассов MemderInfo, такие как IsPublic, IsAbstract и IsSealed. Свойство Attributes возвращает перечисление флагов, которые описывают большинство из них:
| 1 2 3 4 5 6 7 8 | static void Main() {     TypeAttributes ta = typeof (Console).Attributes;     MethodAttributes ma = MethodInfo.GetCurrentMethod().Attributes;     Console.WriteLine (ta + "\r\n" + ma); } /* AutoLayout, AnsiClass, Class, Public, Abstract, Sealed, BeforeFieldInit PrivateScope, Private, Static, HideBySig */ | 
Специальные атрибуты
Специальные атрибуты компилируются в конструкцию, хранимую в главной таблице метаданных типа. Все специальные атрибуты представляют собой подкласс System.Attribute и в отличие от атрибутов с побитовым отображением являются расширяемыми. Конструкция в метаданных идентифицирует класс атрибута и хранит значения всех позиционных и именованных аргументов, которые были указаны во время применения атрибута.
Атрибут объявляется классом, который наследуется от абстрактного класса System.Attribute.
| 1 | public sealed class ObsoleteAttribute : Attribute {...} | 
Чтобы добавить атрибут к элементу кода, нужно перед этим элементом указать название атрибута (типа атрибута) в квадратных скобках.
| 1 2 | [ObsoleteAttribute] public class Foo {...} | 
 По соглашению названия всех типов атрибутов заканчиваются суффиксом Attribute. C# распознает это и позволяет опускать данный суффикс при присоединении атрибута к элементу кода:
| 1 2 | [Obsolete] public class Foo {...} | 
Для одного элемента кода можно добавить несколько атрибутов. При этом либо все атрибуты через запятую приводятся внутри одной пары квадратных скобок, либо каждый атрибут указывается в отдельной паре квадратных скобок:
| 1 2 3 4 | [Serializable, Obsolete, CLSCompliant(false)] public class Bar {...} [Serializable] [Obsolete] [CLSCompliant(false)] public class Bar {...} | 
Именованный (named) и позиционный (positional) параметры атрибутов
У атрибутов могут быть параметры.
| 1 2 | [XmlElement ("Customer", Namespace="http://oreilly.com")] public class CustomerEntity { ... } | 
Параметры атрибутов делятся на позиционный и именованные. В примере выше первый аргумент — позиционный атрибут, второй — именованный. Позиционный параметры соответствуют параметрам конструктора класса (типа) атрибута, именованный — публичным полям и свойствам класса (типа) атрибута.
При добавлении атрибута к элементу кода, позиционные атрибуты должны быть указаны, именованный указываются факультативно.
Цели атрибутов
Как правило целью атрибутов является элемент кода, которому они непосредственно предшествуют. В качестве таких элементов обычно выступают типы или члены типов. В этом случае цель атрибутов специально указывать не надо. Также атрибуты можно присоединить к сборке, но в таком случае цель атрибута должна быть указана явно:
| 1 | [assembly:CLSCompliant(true)] | 
Псевдоспециальные атрибуты
Псевдоспециальные атрибуты (Serializable, StructLayout, In и Out) выглядят и ведут себя подобно обычным специальным атрибутам, с той лишь разницей, что при компиляции они преобразуются в атрибуты с побитовым отображением.
При рефлексии псевдоспециальные атрибуты могут быть получены с помощью специальных свойств (например, IsSerializable), а также с помощью метода GetCustomAttributes. они возвращаются в виде объекта System.Attribute.
AttributeUsage
AttributeUsage — это атрибут, применяемый к классам атрибутов. Он сообщает компилятору, как должен использоваться целевой атрибут:
| 1 2 3 4 5 6 7 | public sealed class AttributeUsageAttribute : Attribute {     public AttributeUsageAttribute (AttributeTargets validOn);     public bool AllowMultiple { get; set; }     public bool Inherited { get; set; }     public AttributeTargets ValidOn { get; } } | 
 Свойство AllowMultiple указывает ,может ли целевой атрибут применяться к одной и той же цели несколько раз. Свойство Inherited указывает, должен ли атрибут, примененный к базовому классу, также применяться к производным классам, а в случае методов — должен ли атрибут, примененный к виртуальному методу, также применяться к переопределенным методам. Свойство ValidOn определяет набор целей (классов, интерфейсов, свойств, методов, параметров и т.д.), к которым может быть присоединен целевой атрибут. Он принимает любую комбинацию значений enum AttributeTargets: All, Delegate, GenericParameter, Parameter, Assembly, Enum, Interface, Property, Class, Event, Method, ReturnValue, Constructor, Field, Module, Struct:
| 1 2 3 4 5 6 | [AttributeUsage (AttributeTargets.Delegate |                  AttributeTargets.Enum |                  AttributeTargets.Struct |                  AttributeTargets.Class, Inherited = false) ] public sealed class SerializableAttribute : Attribute { } | 
Пользовательские атрибуты
Для создания собственного атрибута нужно:
- создать класс, производный от System.Attributeили его потомка; по соглашению имя класса должно заканчиваться словомAttribute(но не обязательно)
- применить к этому классу описанный выше атрибут AttributeUsage
- если атрибут не требует свойств или аргументов конструктора, на этом его создание завершено
- добавить в класс один или несколько публичных конструкторовж параметры конструктора определяют позиционные параметры конструктора и становятся обязательными при использовании атрибута
- объявить публичное свойство или поле для каждого именованного параметра атрибута
Параметры атрибута могут быть экземплярами следующих типов:
- примитивные типы: bool,byte,char,double,float,int,long,shortилиstring
- Type
- enum
- одномерный массив любого из указанных выше типов
| 1 2 3 4 5 6 7 8 9 10 11 | [AttributeUsage (AttributeTargets.Method)] public sealed class TestAttribute : Attribute {     public int Repetitions;     public string FailureMessage;     public TestAttribute () : this (1) { }     public TestAttribute (int repetitions)     {         Repetitions = repetitions;     } } | 
| 1 2 3 4 5 6 7 8 9 | class Foo {     [Test]     public void Method1() { ... }     [Test(20)]     public void Method2() { ... }     [Test(20, FailureMessage="Debugging Time!")]     public void Method3() { ... } } | 
Извлечение атрибутов
Существует два способа извлечения атрибутов:
- вызвать GetCustomAttributesна любом экземпляреTypeилиMemberInfo
- вызвать Attribute.GetCustomAttributeилиAttribute.GetCustomAttributes
Последние два метода перегружены и способны принимать экземпляры следующих типов (соответствующие всем допустимым целям атрибутов): Type, Assembly, Module, MemberInfo или ParameterInfo.
| 1 2 3 4 5 6 7 8 9 10 11 | foreach (MethodInfo mi in typeof (Foo).GetMethods()) {     TestAttribute att = (TestAttribute)     Attribute.GetCustomAttribute (mi, typeof (TestAttribute));     if (att != null)     Console.WriteLine (         "{0} will be tested; reps={1}; msg={2}",         mi.Name,          att.Repetitions,          att.FailureMessage); } | 

