Книга: C# 4.0: полное руководство

Атрибуты

Атрибуты

В C# разрешается вводить в программу информацию декларативного характера в форме атрибута, с помощью которого определяются дополнительные сведения (метаданные), связанные с классом, структурой, методом и т.д. Например, в программе можно указать атрибут, определяющий тип кнопки, которую должен отображать конкретный класс. Атрибуты указываются в квадратных скобках перед тем элементом, к которому они применяются. Следовательно, атрибут не является членом класса, но обозначает дополнительную информацию, присоединяемую к элементу.

Основы применения атрибутов

Атрибут поддерживается классом, наследующим от класса System.Attribute. Поэтому классы атрибутов должны быть подклассами класса Attribute. В классе Attribute определены основные функциональные возможности, но далеко не все они нужны для работы с атрибутами. В именах классов атрибутов принято употреблять суффикс Attribute. Например, ErrorAttribute — это имя класса атрибута, описывающего ошибку.

При объявлении класса атрибута перед его именем указывается атрибут AttributeUsage. Этот встроенный атрибут обозначает типы элементов, к которым может применяться объявляемый атрибут. Так, применение атрибута может ограничиваться одними методами.

Создание атрибута

В классе атрибута определяются члены, поддерживающие атрибут. Классы атрибутов зачастую оказываются довольно простыми и содержат небольшое количество полей или свойств. Например, атрибут может определять примечание, описывающее элемент, к которому присоединяется атрибут. Такой атрибут может принимать следующий вид.

  [AttributeUsage(AttributeTargets.All) ]
public class RemarkAttribute : Attribute {
  string pri_remark; // базовое поле свойства Remark
  public RemarkAttribute(string comment) {
    pri_remark = comment;
  }
  public string Remark {
    get {
      return pri_remark;
    }
  }
}

Проанализируем этот класс атрибута построчно.

Объявляемый атрибут получает имя RemarkAttribute. Его объявлению предшествует встроенный атрибут AttributeUsage, указывающий на то, что атрибут RemarkAttribute может применяться ко всем типам элементов. С помощью встроенного атрибута AttributeUsage можно сузить перечень элементов, к которым может присоединяться объявляемый атрибут. Подробнее о его возможностях речь пойдет далее в этой главе.

Далее объявляется класс RemarkAttribute, наследующий от класса Attribute. В классе RemarkAttribute определяется единственное закрытое поле pri_remark, поддерживающее одно открытое и доступное для чтения свойство Remark. Это свойство содержит описание, связываемое с атрибутом. (Конечно, Remark можно было бы объявить как автоматически реализуемое свойство с закрытым аксессором set, но ради наглядности данного примера выбрано свойство, доступное только для чтения.) В данном классе определен также один открытый конструктор, принимающий строковый аргумент и присваивающий его свойству Remark. Этим пока что ограничиваются функциональные возможности класса RemarkAttribute, готового к применению.

Присоединение атрибута

Как только класс атрибута будет определен, атрибут можно присоединить к элементу. Атрибут указывается перед тем элементом, к которому он присоединяется, и для этого его конструктор заключается в квадратные скобки. В качестве примера ниже показано, как атрибут RemarkAttribute связывается с классом.

[RemarkAttribute("В этом классе используется атрибут.")]
class UseAttrib {
// ...
}

В этом фрагменте кода конструируется атрибут RemarkAttribute, содержащий комментарий "В этом классе используется атрибут ." Данный атрибут затем связывается с классом UseAttrib.

Присоединяя атрибут, совсем не обязательно указывать суффикс Attribute. Например, приведенный выше класс может быть объявлен следующим образом.

[Remark("В этом классе используется атрибут.")] class UseAttrib {
// . . .
}

В этом объявлении указывается только имя Remark. Такая сокращенная форма считается вполне допустимой, но все же надежнее указывать полное имя присоединяемого атрибута, чтобы избежать возможной путаницы и неоднозначности.

Получение атрибутов объекта

Как только атрибут будет присоединен к элементу, он может быть извлечен в других частях программы. Для извлечения атрибута обычно используется один из двух методов. Первый метод, GetCustomAttributes(), определяется в классе MemberInfо и наследуется классом Туре. Он извлекает список всех атрибутов, присоединенных к элементу. Ниже приведена одна из его форм.

object[] GetCustomAttributes(bool наследование)

Если наследование имеет логическое значение true, то в список включаются атрибуты всех базовых классов, наследуемых по иерархической цепочке. В противном случае атрибуты извлекаются только из тех классов, которые определяются указанным типом.

Второй метод, GetCustomAttribute(), определяется в классе Attribute. Ниже приведена одна из его форм:

static Attribute GetCustomAttribute(Memberlnfо элемент, Туре тип_атрибута)

где элемент обозначает объект класса MemberInfо, описывающий тот элемент, для которого создаются атрибуты, тогда как тип_атрибута — требуемый атрибут. Данный метод используется в том случае, если цмя получаемого атрибута известно заранее, что зачастую и бывает. Так, если в классе UseAttrib имеется атрибут RemarkAttribute, то для получения ссылки на этот атрибут можно воспользоваться следующей последовательностью кода.

// Получить экземпляр объекта класса MemberInfо, связанного
// с классом, содержащим атрибут RemarkAttribute.
Type t = typeof(UseAttrib);
// Извлечь атрибут RemarkAttribute.
Type tRemAtt = typeof(RemarkAttribute);
RemarkAttribute ra = (RemarkAttribute)
         Attribute.GetCustomAttribute(t, tRemAtt);

Эта последовательность кода оказывается вполне работоспособной, поскольку класс MemberInfo является базовым для класса Туре. Следовательно, t — это экземпляр объекта класса MemberInfo.

Имея ссылку на атрибут, можно получить доступ к его членам. Благодаря этому информация об атрибуте становится доступной для программы, использующей элемент, к которому присоединен атрибут. Например, в следующей строке кода выводится содержимое свойства Remark.

Console.WriteLine(га.Remark);

Ниже приведена программа, в которой все изложенные выше особенности применения атрибутов демонстрируются на примере атрибута RemarkAttribute.

// Простой пример применения атрибута.
using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.All)]
public class RemarkAttribute : Attribute {
  string pri_remark; // базовое поле свойства Remark
  public RemarkAttribute(string comment) {
    pri_remark = comment;
  }
  public string Remark {
    get {
      return pri_remark;
    }
  }
}
[RemarkAttribute("В этом классе используется атрибут.")]
class UseAttrib {
  // ...
}
class AttribDemo {
  static void Main() {
    Type t = typeof(UseAttrib);
    Console.Write("Атрибуты в классе " + t.Name + ": ");
    object[] attribs = t.GetCustomAttributes(false);
    foreach (object о in attribs) {
      Console.WriteLine(о);
    }
    Console.Write("Примечание: ");
    // Извлечь атрибут RemarkAttribute.
    Type tRemAtt = typeof(RemarkAttribute);
    RemarkAttribute ra = (RemarkAttribute)
    Attribute.GetCustomAttribute(t, tRemAtt);
    Console.WriteLine(ra.Remark);
  }
}

Эта программа дает следующий результат.

Атрибуты в классе UseAttrib: RemarkAttribute
Примечание: В этом классе используется атрибут.

Сравнение позиционных и именованных параметров

В предыдущем примере для инициализации атрибута RemarkAttribute его конструктору была передана символьная строка с помощью обычного синтаксиса конструктора. В этом случае параметр comment конструктора RemarkAttribute() называется позиционным. Этот термин отражает тот факт, что аргумент связан с параметром по его позиции в списке аргументов. Следовательно, первый аргумент передается первому параметру, второй аргумент — второму параметру и т.д.

Но для атрибута доступны также именованные параметры, которым можно присваивать первоначальные значения по их именам. В этом случае значение имеет имя, а не позиция параметра.

---------------------------------------

ПРИМЕЧАНИЕ

Несмотря на то что именованные параметры атрибутов, по существу, подобны именованным аргументам методов, они все же отличаются в деталях.

---------------------------------------

Именованный параметр поддерживается открытым полем или свойством, которое должно быть нестатическим и доступным только для записи. Любое поле или свойство подобного рода может автоматически использоваться в качестве именованного параметра. Значение присваивается именованному параметру с помощью соответствующего оператора, расположенного в списке аргументов при вызове конструктора атрибута. Ниже приведена общая форма объявления атрибута, включая именованные параметры.

[attrib(список_позиционных_параметров,
именованный_параметр_1 = значение,
именованный_параметр_2 = значение,    ...)]

Первыми указываются позиционные параметры, если они существуют. Далее следуют именованные параметры с присваиваемыми значениями. Порядок следования именованных параметров особого значения не имеет. Именованным параметрам не обязательно присваивать значение, и в этом случае используется значение, устанавливаемое по умолчанию.

Применение именованного параметра лучше всего показать на конкретном примере. Ниже приведен вариант класса RemarkAttribute, в который добавлено поле Supplement, предназначенное для хранения дополнительного примечания.

[AttributeUsage(AttributeTargets.All)]
public class RemarkAttribute : Attribute {
  string pri_remark; // базовое поле свойства Remark
  // Это поле можно использовать в качестве именованного параметра,
  public string Supplement;
  public RemarkAttribute(string comment) {
    pri_remark = comment;
    Supplement = "Отсутствует";
  }
  public string Remark {
    get {
      return pri_remark;
    }
  }
}

Как видите, поле Supplement инициализируется в конструкторе символьной строкой "Отсутствует". Другого способа присвоить ему первоначальное значение в конструкторе не существует. Но поскольку поле Supplement является открытым в классе RemarkAttribute, его можно использовать в качестве именованного параметра, как показано ниже.

[RemarkAttribute("В этом классе используется атрибут.",
Supplement = "Это дополнительная информация.")]
class UseAttrib {
// ...
}

Обратите особое внимание на вызов конструктора класса RemarkAttribute. В этом конструкторе первым, как и прежде, указывается позиционный параметр, а за ним через запятую следует именованный параметр Supplement, которому присваивается конкретное значение. И наконец, закрывающая скобка, ), завершает вызов конструктора. Таким образом, именованный параметр инициализируется в вызове конструктора. Этот синтаксис можно обобщить: позиционные параметры должны указываться в том порядке, в каком они определены в конструкторе, а именованные параметры — в произвольном порядке и вместе с присваиваемыми им значениями.

Ниже приведена программа, в которой демонстрируется применение поля Supplement в качестве именованного параметра атрибута.

// Использовать именованный параметр атрибута.
using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.All)]
public class RemarkAttribute : Attribute {
  string pri_remark; // базовое поле свойства Remark
  public string Supplement; // это именованный параметр
  public RemarkAttribute(string comment) {
    pri_remark = comment;
    Supplement = "Отсутствует";
  }
  public string Remark {
    get {
      return pri_remark;
    }
  }
}
[RemarkAttribute("В этом классе используется атрибут.",
Supplement = "Это дополнительная информация.")]
class UseAttrib {
// ...
}
class NamedParamDemo {
  static void Main() {
    Type t = typeof(UseAttrib);
    Console.Write("Атрибуты в классе " + t.Name + ": ");
    object[] attribs = t.GetCustomAttributes(false);
    foreach(object o in attribs) {
      Console.WriteLine (o);
    }
    // Извлечь атрибут RemarkAttribute.
    Type tRemAtt = typeof(RemarkAttribute);
    RemarkAttribute ra = (RemarkAttribute)
    Attribute.GetCustomAttribute(t, tRemAtt);
    Console.Write("Примечание: ");
    Console.WriteLine(ra.Remark);
    Console.Write("Дополнение: ") ;
    Console.WriteLine(ra.Supplement);
  }
}

При выполнении этой программы получается следующий результат.

Атрибуты в классе UseAttrib: RemarkAttribute
Примечание: В этом классе используется атрибут.
Дополнение: Это дополнительная информация.

Прежде чем перейти к следующему вопросу, следует особо подчеркнуть, что поле pri_remark нельзя использовать в качестве именованного параметра, поскольку оно закрыто в классе RemarkAttribute. Свойство Remark также нельзя использовать в качестве именованного параметра, потому что оно доступно только для чтения. Напомним, что в качестве именованных параметров могут служить только открытые поля и свойства.

Открытое и доступное только для чтения свойство может использоваться в качестве именованного параметра таким же образом, как и открытое поле. В качестве примера ниже показано, как автоматически реализуемое свойство Priority типа int вводится в класс RemarkAttribute.

// Использовать свойство в качестве именованного параметра атрибута.
using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.All)]
public class RemarkAttribute : Attribute {
  string pri_remark; // базовое поле свойства Remark
  public string Supplement; // это именованный параметр
  public RemarkAttribute(string comment) {
    pri_remark = comment;
    Supplement = "Отсутствует";
    Priority = 1;
  }
  public string Remark {
    get {
      return pri_remark;
    }
  }
  // Использовать свойство в качестве именованного параметра,
  public int Priority { get; set; }
}
[RemarkAttribute("В этом классе используется атрибут.",
        Supplement = " Это дополнительная информация.",
        Priority = 10)]
class UseAttrib {
// ...
}
class NamedParamDemo {
  static void Main() {
    Type t = typeof(UseAttrib);
    Console.Write("Атрибуты в классе " + t.Name + ": ");
    object[] attribs = t.GetCustomAttributes(false);
    foreach(object o in attribs) {
      Console.WriteLine(o);
    }
    // Извлечь атрибут RemarkAttribute.
    Type tRemAtt = typeof(RemarkAttribute);
    RemarkAttribute ra = (RemarkAttribute)
    Attribute.GetCustomAttribute(t, tRemAtt);
    Console.Write("Примечание: ") ;
    Console.WriteLine(ra.Remark);
    Console.Write("Дополнение: ") ;
    Console.WriteLine(ra.Supplement);
    Console.WriteLine("Приоритет: " + ra.Priority);
  }
}

Вот к какому результату приводит выполнение этого кода.

Атрибуты в классе UseAttrib: RemarkAttribute
Примечание: В этом классе используется атрибут.
Дополнение: Это дополнительная информация.
Приоритет: 10

В данном примере обращает на себя внимание порядок указания атрибутов перед классом UseAttrib, как показано ниже.

[RemarkAttribute("В этом классе используется атрибут.",
      Supplement = " Это дополнительная информация.",
      Priority = 10)]
class UseAttrib {
// ...
}

Именованные параметры атрибутов Supplement и Priority не обязательно указывать в каком-то определенном порядке. Порядок их указания можно свободно изменить, не меняя сами атрибуты.

И последнее замечание: тип параметра атрибута (как позиционного, так и именованного) должен быть одним из встроенных простых типов, object, Туре, перечислением или одномерным массивом одного из этих типов.

Оглавление книги


Генерация: 0.045. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз