Извлечение экземпляра атрибута во время выполнения
Компиляция атрибута выдает класс атрибута T, конструктор экземпляров C в T, список_позиционных_аргументов P и список_именованных_аргументов N. Если даны эти сведения, экземпляр атрибута может быть извлечен во время выполнения с помощью следующих шагов:
· выполнение шагов обработки времени выполнения для выполнения выражения_создания_объекта вида new T(P) с помощью конструктора экземпляров C, как определено во время компиляции. Эти шаги приводят либо к исключению, либо к созданию экземпляра O объекта T;
· для каждого именованного аргумента Arg в N в следующем порядке:
o разрешение Name быть идентификатором именованного аргумента Arg; Если Name не идентифицирует нестатическое, открытое, с доступом для чтения и записи поле или свойство в O, вызывается исключение;
o разрешение Value быть результатом вычисления выражения_аргумента_атрибута Arg;
o если Name определяет поле в O, установить в этом поле значение Value;
o в противном случае Name определяет свойство в O. Задать для данного свойства значение Value;
o результатом является O, экземпляр класса атрибута T, инициализированный с помощью списка_позиционных_аргументов P и списка_именованных_аргументов N.
Зарезервированные атрибуты
Небольшое число атрибутов некоторым образом влияют на язык. К этим атрибутам относятся:
· System.AttributeUsageAttribute (§17.4.1), используемый для описания способов, которыми можно использовать класс атрибута;
· System.Diagnostics.ConditionalAttribute (§17.4.2), используемый для определения условных методов;
· System.ObsoleteAttribute (§17.4.3), используемый для пометки члена как устаревшего;
· System.Runtime.CompilerServices.CallerLineNumberAttribute, System.Runtime.CompilerServices.CallerFilePathAttribute и System.Runtime.CompilerServices.CallerMemberNameAttribute (§17.4.4), используемые для передачи данных о вызывающем контексте необязательным параметрам.
Атрибут AttributeUsage
Атрибут AttributeUsage используется для описания способа использования класса атрибута.
Класс, к которому добавлен атрибут AttributeUsage, должен прямо или косвенно наследоваться от класса System.Attribute. Иначе возникает ошибка времени компиляции.
namespace System
{
[AttributeUsage(AttributeTargets.Class)]
public class AttributeUsageAttribute: Attribute
{
public AttributeUsageAttribute(AttributeTargets validOn) {...}
public virtual bool AllowMultiple { get {...} set {...} }
public virtual bool Inherited { get {...} set {...} }
public virtual AttributeTargets ValidOn { get {...} }
}
public enum AttributeTargets
{
Assembly = 0x0001,
Module = 0x0002,
Class = 0x0004,
Struct = 0x0008,
Enum = 0x0010,
Constructor = 0x0020,
Method = 0x0040,
Property = 0x0080,
Field = 0x0100,
Event = 0x0200,
Interface = 0x0400,
Parameter = 0x0800,
Delegate = 0x1000,
ReturnValue = 0x2000,
All = Assembly | Module | Class | Struct | Enum | Constructor |
Method | Property | Field | Event | Interface | Parameter |
Delegate | ReturnValue
}
}
Атрибут Conditional
Атрибут Conditionalпозволяет определять условные методы и классы условных атрибутов.
namespace System.Diagnostics
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
AllowMultiple = true)]
public class ConditionalAttribute: Attribute
{
public ConditionalAttribute(string conditionString) {...}
public string ConditionString { get {...} }
}
}
Условные методы
Метод, к которому добавлен атрибут Conditional, является условным методом. Атрибут Conditional указывает условие путем проверки символа условной компиляции. Вызовы условного метода или включаются, или опускаются, в зависимости от того, определен ли этот символ в точке вызова. Если символ определен, вызов включается; иначе вызов (включая оценку выражения-получателя и параметров вызова) опускается.
Для условного метода имеются следующие ограничения:
· Условный метод должен быть методом в объявлении_класса или объявлении_структуры. Если атрибут Conditional указан для метода в объявлении интерфейса, возникает ошибка времени компиляции.
· Условный метод должен иметь тип возвращаемого значения void.
· Условный метод не должен быть помечен модификатором override. Однако условный метод может быть помечен модификатором virtual. Переопределения такого метода являются неявно условными и они не должны явно помечаться атрибутом Conditional.
· условный метод не должен быть реализацией метода интерфейса. Иначе возникает ошибка времени компиляции.
Кроме того, ошибка времени компиляции возникает, если условный метод используется в выражении_создания_делегата. Пример:
#define DEBUG
using System;
using System.Diagnostics;
class Class1
{
[Conditional("DEBUG")]
public static void M() {
Console.WriteLine("Executed Class1.M");
}
}
class Class2
{
public static void Test() {
Class1.M();
}
}
Class1.M объявляется как условный метод. Этот метод вызывается методом Test класса Class2. Так как символ условной компиляции DEBUG определен, если вызывается метод Class2.Test, он вызовет M. Если бы символ DEBUG не был определен, метод Class2.Test не вызвал бы Class1.M.
Обратите внимание, что включение или исключение вызова условного метода управляется символами условной компиляции в точке вызова. В этом примере
Файл class1.cs:
using System.Diagnostics;
class Class1
{
[Conditional("DEBUG")]
public static void F() {
Console.WriteLine("Executed Class1.F");
}
}
Файл class2.cs:
#define DEBUG
class Class2
{
public static void G() {
Class1.F(); // F is called
}
}
Файл class3.cs:
#undef DEBUG
class Class3
{
public static void H() {
Class1.F(); // F is not called
}
}
каждый из классов Class2 и Class3 содержит вызовы условного метода Class1.F, вызов которого обусловленного тем, определен или нет символ DEBUG. Так как этот символ определен в контексте Class2, но не Class3, вызов F в Class2 включен, тогда как вызов F в Class3 опущен.
Использование условных методов в цепочке наследования может привести к путанице. Вызовы условного метода посредством base в виде base.M подчиняются обычным правилам вызова условного метода. В этом примере
Файл class1.cs:
using System;
using System.Diagnostics;
class Class1
{
[Conditional("DEBUG")]
public virtual void M() {
Console.WriteLine("Class1.M executed");
}
}
Файл class2.cs:
using System;
class Class2: Class1
{
public override void M() {
Console.WriteLine("Class2.M executed");
base.M(); // base.M is not called!
}
}
Файл class3.cs:
#define DEBUG
using System;
class Class3
{
public static void Test() {
Class2 c = new Class2();
c.M(); // M is called
}
}
Class2 включает вызов метода M, определенного в его базовом классе. Этот вызов опускается, так как базовый метод является условным, базированным на наличии символа DEBUG, который здесь не определен. Таким образом, метод записывает на консоль только сообщение "Class2.M executed". Разумное использование ПРО_описаний может устранить такие проблемы.
Классы условных атрибутов
Класс атрибута (§17.1), к которому добавлен один или несколько атрибутов Conditional, является классом условных атрибутов. Таким образом, класс условных атрибутов связывается с символами условной компиляции, объявленными в его атрибутах Conditional. Рассмотрим следующий пример:
using System;
using System.Diagnostics;
[Conditional("ALPHA")]
[Conditional("BETA")]
public class TestAttribute : Attribute {}
объявлен атрибут TestAttribute в качестве класса условных атрибутов, связанного с символами условной компиляции ALPHA и BETA.
Спецификации атрибута (§17.2) условного атрибута включаются, если в точке спецификации определен один или несколько его связанных символов условной компиляции, иначе спецификация атрибута опускается.
Обратите внимание, что включение или исключение спецификации атрибута класса условных атрибутов управляется символами условной компиляции в точке спецификации. В этом примере
Файл test.cs:
using System;
using System.Diagnostics;
[Conditional("DEBUG")]
public class TestAttribute : Attribute {}
Файл class1.cs:
#define DEBUG
[Test] // TestAttribute is specified
class Class1 {}
Файл class2.cs:
#undef DEBUG
[Test] // TestAttribute is not specified
class Class2 {}
к каждому из классов Class1 и Class2 добавляется атрибут Test, который является условным и зависит от того, определен или нет символ DEBUG. Так как этот символ определен в контексте Class1, но не в контексте Class2, спецификация атрибута Test для Class1 включена, тогда как спецификация атрибута Test для Class2 опущена.
Атрибут Obsolete
Атрибут Obsolete используется для пометки типов и членов типов, которые не следует больше использовать.
namespace System
{
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Struct |
AttributeTargets.Enum |
AttributeTargets.Interface |
AttributeTargets.Delegate |
AttributeTargets.Method |
AttributeTargets.Constructor |
AttributeTargets.Property |
AttributeTargets.Field |
AttributeTargets.Event,
Inherited = false)
]
public class ObsoleteAttribute: Attribute
{
public ObsoleteAttribute() {...}
public ObsoleteAttribute(string message) {...}
public ObsoleteAttribute(string message, bool error) {...}
public string Message { get {...} }
public bool IsError { get {...} }
}
}
Если программа использует тип или член, к которому добавлен атрибут Obsolete, компилятор выдает предупреждение или ошибку. А именно, компилятор выдает предупреждение, если не предоставлен параметр ошибки или если параметр ошибки имеет значение false. Компилятор выдает сообщение об ошибке, если указан параметр ошибки со значением true.
В этом примере
[Obsolete("This class is obsolete; use class B instead")]
class A
{
public void F() {}
}
class B
{
public void F() {}
}
class Test
{
static void Main() {
A a = new A(); // Warning
a.F();
}
}
к классу A добавляется атрибут Obsolete. Каждое использование A в Main приводит к выдаче предупреждения, в состав которого включено сообщение "Этот класс устаревший; используйте вместо него класс B".