• СТАТЬЯ  Использование атрибутов в среде .NET
  • Программирование на Visual C++

    Выпуск №68 от 17 марта 2002 г.

    Здравствуйте, уважаемые подписчики!

    СТАТЬЯ 

    Использование атрибутов в среде .NET

    Автор: Алифанов Андрей

    Демонстрационный проект

    АТРИБУТ. Необходимый, постоянный признак, принадлежность.

    ("Толковый словарь русского языка", С.И. Ожегов)
    Введение

    Если вы когда-либо программировали на C++, вам должны быть знакомы определения, такие как public и private, предоставляющие дополнительную информацию о членах класса. Эти ключевые слова задают поведение членов класса, описывая их доступность извне. Так как компиляторы распознают только предопределенные ключевые слова, вы не имеете возможности создавать свои собственные.

    CLR, однако, позволяет вам добавлять объявления, называемые атрибутами, для комментирования таких элементов кода как типы, поля, методы и свойства. Внешне эти атрибуты похожи на те, что используются в языке описания интерфейсов IDL, но есть и существенные отличия: во-первых, система атрибутов CLR является расширяемой, а во-вторых, используется не только для управления маршалингом данных, а гораздо шире.

    Когда вы компилируете свой код, он преобразуется в Microsoft Intermediate Language (MSIL) и помещается в файл формата Portable Executable (PE) вместе с метаданными, сгенерированными компилятором. Атрибуты позволяют добавить к метаданным дополнительную информацию, которая затем может извлекаться при помощи механизма рефлексии. Компилятор создает атрибуты, когда вы объявляете экземпляры специальных классов, наследующих от System.Attribute.

    .NET Framework широко использует атрибуты. Атрибуты описывают правила сериализации данных, управляют безопасностью и ограничивают оптимизацию JIT-компиляторов для облегчения отладки кода. Атрибуты также могут содержать имя файла или автора, или управлять видимостью элементов управления и классов при разработке форм пользовательского интерфейса.

    Вы можете использовать атрибуты для произвольного комментирования кода и управления поведением компонентов. Атрибуты позволяют добавлять описательные элементы в C#, управляемые расширения для C++, Microsoft Visual Basic.NET или в любой другой язык, поддерживающий CLR, без необходимости переписывать компилятор.

    Кроме того, атрибуты можно использовать в ATL проектах, но это тема уже другой статьи.

    Применение атрибутов

    Большинство атрибутов применяется к таким элементам языка как классы, методы, поля и свойства. Но некоторые атрибуты являются глобальными – они воздействуют на всю сборку или модуль. Глобальные атрибуты в текстах программ объявляются после using директив верхнего уровня перед определениями типов и пространств имен. Они могут использоваться в разных исходных файлах одной программы.

    Применение атрибутов на уровне классов и методов

    Атрибуты в программном коде используются следующим образом:

    1. Определяется новый или берется существующий в .NET Framework атрибут

    2. Инициализируется конкретный экземпляр атрибута с помощью вызова конструктора атрибута.

    Атрибут помещается в метаданные при компиляции кода и становится доступен CLR и любым инструментальным средствам и приложениям через механизмы рефлексии.

    По соглашению, имена всех атрибутов оканчиваются словом Attribute. Однако, языки из VisualStudio.NET, не требуют задания полного имени атрибута. Например, если нужно инициализировать атрибут System.ObsoleteAttribute, достаточно написать Obsolete.

    Следующий пример показывает, как использовать атрибут System.ObsoleteAttribute, помечающий код как устаревший. Атрибуту передается строка "Будет удалено в следующей версии". Этот атрибут заставляет компилятор выдать переданную строку как предупреждение при компиляции помеченного кода.

    C#

    using System;

    public class MainApp {

     public static void Main() {

      //На этой строке компилятор выдаст предупреждение.

      int MyInt = Add(2,2);

     }


     //В C# атрибуты задаются в квадратных скобках.

     //Этот атрибут применяется только к методу Add.

     [Obsolete("В следующей версии метод будет удален")]

     public static int Add(int a, int b) {

      return (a + b);

     }

    }

    MC++

    #using <mscorlib.dll>

    using namespace System;

    int Add(int a, int b);


    void main(void) {

     //На этой строке компилятор выдаст предупреждение.

     int MyInt = Add(2, 2);

     return;

    }


    //В MC++ атрибуты задаются в квадратных скобках.

    //Этот атрибут применяется только к методу Add.

    [Obsolete(S"В следующей версии метод будет удален")]

    int Add(int a, int b) {

     return (a + b);

    }

    Visual Basic.NET

    Imports System

    Public Module main

    Sub Main()

     'На этой строке компилятор выдаст предупреждение.

     Dim MyInt as Integer = Add(2,2)

    End Sub


    ' В Visual Basic.NET атрибуты задаются между скобками < и >.

    ' Этот атрибут применяется только к методу Add.

    Function <Obsolete("В следующей версии метод будет удален")>_

     Add(a as Integer, b as Integer) as Integer

     Add = a + b

    End Function

    End Module

    Применение атрибутов на уровне сборок

    Для применения атрибутов на уровне сборок используется ключевое слово Assembly. Следующий пример показывает, как используется атрибут AssemblyNameAttribute:

    C#

    using System.Reflection;

    [assembly:AssemblyName("Моя сборка")]

    MC++

    using namespace System::Reflection;

    [assembly:AssemblyName(S"Моя сборка")];

    Visual Basic.NET

    Imports System.Reflection

    <Assembly:AssemblyName("Моя сборка")>

    При компиляции кода строка "Моя сборка" помещается в манифест сборки в секции метаданных. Этот атрибут можно увидеть с помощью дизассемблера MSIL (Ildasm.exe) или с помощью пользовательских средств.

    Применение атрибутов на уровне модулей

    Для применения атрибутов на уровне модулей используется ключевое слово Module, в остальном все как на уровне сборок.

    Пользовательские атрибуты

    Чтобы разрабатывать собственные атрибуты, не нужно изучать что-то принципиально новое. Если вы знакомы с объектно-ориентированным программированием и знаете, как разрабатывать классы, вы знаете уже практически все. Пользовательские атрибуты – это классы, тем или иным образом наследующие от System.Attribute. Также как и все другие классы, пользовательские атрибуты содержат методы для записи и чтения данных. Рассмотрим процесс создания пользовательского атрибута по шагам.

    Применение атрибута AttributeUsageAttribute

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

    C#

    [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]

    MC++

    [AttributeUsage(AttributeTargets::All, Inherited = false, AllowMultiple = true)]

    Visual Basic.NET

    <AttributeUsage(AttributeTargets.All, Inherited := False, AllowMultiple := true)>

    Класс System.AttributeUsageAttribute содержит три члена, которые важны для создания пользовательских атрибутов: AttributeTargets, Inherited и AllowMultiple.

    Поле AttributeTargets

    В предыдущем примере используется флаг AttributeTargets.All. Этот флаг означает, что данный атрибут может применяться к любым элементам программы. С другой стороны, можно задать флаг AttributeTargets.Class, означающий, что атрибут применяется только к классам, или AttributeTargets.Method – для методов классов и интерфейсов. Подобным образом можно применять и пользовательские атрибуты.

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

    C#

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]

    MC++

    [AttributeUsage(AttributeTargets::Class | AttributeTargets::Method)]

    Visual Basic.NET

    <AttributeUsage(AttributeTargets.Class BitOr AttributeTargets.Method)>

    Свойство Inherited

    Это свойство определяет, будет ли атрибут наследоваться классами, наследниками того, к которому этот атрибут применен. Это свойство может принимать два значения: true или false.

    C#

    // По умолчанию Inherited = true.

    public class MyAttribute : Attribute {}

    // Явно задается false.

    [AttributeUsage(Inherited = false)]

    public class YourAttribute : Attribute {}

    MC++

    // По умолчанию Inherited = true.

    public gc class MyAttribute : public System::Attribute {}

    // Явно задается false.

    [AttributeUsage(Inherited = false)]

    public gc class YourAttribute : public System::Attribute {}

    Visual Basic.NET

    ' По умолчанию Inherited := true.

    Public Class  _

     <AttributeUsage(AttributeTargets.All, Inherited := True)> MyAttribute

     Inherits Attribute

    End Class


    Public Class _

     <AttributeUsage(AttributeTargets.All, Inherited := False)> YourAttribute

     Inherits Attribute

    End Class

    Вышеописанные атрибуты затем применяются к методу класса MyClass:

    C#

    public class MyClass {

     // В C# несколько атрибутов могут определяться в разных блоках,

     // ограниченных скобками или в одном блоке – через запятую.

     // Порядок следования атрибутов неважен.

     [MyAttribute][YourAttribute]

     public void MyMethod() {

      //…

     }

    }

    MC++

    public gc class MyClass {

    public:

     // В MC++ несколько атрибутов могут определяться в разных блоках,

     // ограниченных скобками или в одном блоке – через запятую.

     // Порядок следования атрибутов неважен.

    [MyAttribute][YourAttribute]

     void MyMethod() {

      //…

     }

    }

    Visual Basic.NET

    ' В Microsoft Visual Basic.NET несколько атрибутов разделяются запятыми.

    ' Порядок следования атрибутов неважен.

    Public Class MyClass

     Public Sub <MyAttribute, YourAttribute> MyMethod()

      '…

     End Sub

    End Class

    И, наконец, рассмотрим класс YourClass – наследник MyClass. С методом MyMethod этого класса будет связан только атрибут MyAttribute.

    C#

    public class YourClass : MyClass {

     // Этот метод имеет только атрибут MyAttribute.

     public void MyMethod() {

      //…

     }

    }

    MC++

    public gc class YourClass : public MyClass {

    public:

     // Этот метод имеет только атрибут MyAttribute.

     void MyMethod() {

      //…

     }

    }

    Visual Basic.NET

    Public Class YourClass

     Inherits MyClass

     ' Этот метод имеет только атрибут MyAttribute.

     Public Sub MyMethod()

      '…

     End Sub

    End Class

    Свойство AllowMultiple

    Это свойство показывает, может ли атрибут применяться многократно к одному элементу. По умолчанию оно равно false, что значит – атрибут может использоваться только один раз. Рассмотрим следующий пример:

    C#

    // По умолчанию AllowMultiple = false.

    public class MyAttribute : Attribute {}

    [AttributeUsage(AllowMultiple = true)]

    public class YourAttribute : Attribute {}

    MC++

    // По умолчанию AllowMultiple = false.

    public gc class MyAttribute : public System::Attribute {}

    [AttributeUsage(AllowMultiple = true)]

    public gc class YourAttribute : public System::Attribute {}

    Visual Basic.NET

    ' По умолчанию AllowMultiple = false.

    Public Class _

     <AttributeUsage(AttributeTargets.Method)> MyAttribute

     Inherits Attribute

    End Class


    Public Class _

     <AttributeUsage(AttributeTargets.Method, AllowMultiple := True)> YourAttribute

     Inherits Attribute

    End Class

    Если используется несколько экземпляров атрибутов, MyAttribute заставляет компилятор выдать сообщение об ошибке. Следующий фрагмент кода иллюстрирует правильное использование атрибута YourAttribute и неправильное – MyAttribute:

    C#

    public class MyClass {

     // Ошибка – дублирование не разрешено.

     [MyAttribute, MyAttribute]

     public void MyMethod() {

      //…

     }


     // Это допустимо.

     [YourAttribute, YourAttribute] public void YourMethod() {

      //…

     }

    }

    MC++

    public gc class MyClass {

    public:

     // Ошибка – дублирование не разрешено.

     [MyAttribute, MyAttribute] void MyMethod() {

      //…

     }


     // Это допустимо.

     [YourAttribute, YourAttribute] void YourMethod() {

      //…

     }

    }

    Visual Basic.NET

    Public Class MyClass

     ' Ошибка – дублирование не разрешено.

     Public Sub <MyAttribute, MyAttribute> MyMethod()

      '…

     End Sub


     ' Это допустимо.

     Public Sub <YourAttribute, YourAttribute> YourMethod()

      '…

     End Sub

    End Class

    Если свойства AllowMultiple и Inherited установлены в true, класс может наследовать атрибут и иметь еще экземпляры, примененные непосредственно к нему. Если же свойство AllowMultiple равно false, значения атрибутов родительского класса будут переписаны значениями этого же атрибута класса-наследника.

    Типы данных, допустимые в атрибутах

    Атрибут может содержать поля следующих типов:

    • Bool

    • Byte

    • Char

    • Double

    • Float

    • Int

    • Long

    • Short

    • String

    • Object

    • System.Type

    Открытые перечислимые типы, вложенные (если вложены) в открытые типы

    Попытка использовать в классе, реализующем атрибут другие типы, приводит к ошибкам компиляции.

    Определение атрибутивного класса

    Теперь можно приступить к определению самого класса. Это определение выглядит подобно определению обычного класса, что демонстрирует следующий пример:

    C#

    // Этот атрибут может применяться только к методам

    public class MyAttribute : System.Attribute {

     // …

    }

    MC++

    // Этот атрибут может применяться только к методам

    public gc class MyAttribute : System.Attribute {

     // …

    }

    Visual Basic.NET

    ' Этот атрибут может применяться только к методам

    Public Class <AttributeUsage(AttributeTargets.Method)> MyAttribute

     Inherits System.Attribute

     ' …

    End Class

    Этот пример показывает следующие положения:

    • Атрибутивные классы должны объявляться как открытые

    • По соглашению, имена классов должны заканчиваться словом Attribute. Хотя это и необязательно, рекомендуется поступать так для улучшения читаемости текста. При использовании атрибута это слово необязательно.

    • Все атрибутивные классы должны, так или иначе, наследовать от System.Attribute.

    • В Microsoft Visual Basic все пользовательские атрибутивные классы должны иметь атрибут AttributeUsageAttribute.

    Определение конструкторов

    Атрибуты инициализируются конструкторами, так же как обычные классы. Следующий фрагмент кода иллюстрирует типичный конструктор атрибута. Этот открытый конструктор принимает один параметр и инициализирует переменную класса.

    C#

    public MyAttribute(bool myvalue) {

     this.myvalue = myvalue;

    }

    MC++

    public:

     MyAttribute(bool myvalue) {

      this->myvalue = myvalue;

     }

    Visual Basic.NET

    Public Sub New(newvalue As Boolean)

     Me.myvalue = newvalue

    End Sub

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

    Следующий пример показывает примеры использования параметризованного конструктора для инициализации атрибута. Здесь предполагается, что атрибут имеет обязательный параметр типа Boolean и необязательный типа String.

    C#

    // Один обязательный (позиционный) и один

    // необязательный (именованный) параметры.

    [MyAttribute(false, OptionalParameter = "дополнительные данные")]

    // Один обязательный (позиционный) параметр.

    [MyAttribute(false)]

    MC++

    // Один обязательный (позиционный) и один необязательный

    //(именованный) параметры.

    [MyAttribute(false, OptionalParameter = S"дополнительные данные")]

    // Один обязательный (позиционный) параметр.

    [MyAttribute(false)]

    Visual Basic.NET

    ' Один обязательный (позиционный) и один необязательный

    '(именованный) параметры.

    <MyAttribute(False, OptionalParameter := "дополнительные данные")>

    ' …

    ' Один обязательный (позиционный) параметр.

    <MyAttribute(False)>

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

    C#

    // Именованный параметр помещается перед позиционным.

    [MyAttribute(OptionalParameter = "дополнительные данные", false)]

    MC++

    // Именованный параметр помещается перед позиционным.

    [MyAttribute(OptionalParameter = S"дополнительные данные", false)]

    Visual Basic.NET

    ' Именованный параметр помещается перед позиционным.

    <MyAttribute(OptionalParameter := "дополнительные данные" , False)>

    Определение свойств

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

    C#

    public bool MyProperty {

     get {

      return this.myvalue;

     }

     set {

      this.myvalue = value;

     }

    }

    MC++

    public:

     __property bool get_MyProperty() { return myvalue; }

     __property void set_MyProperty(bool value) { myvalue = value; }

    protected:

     bool myvalue;

    }

    Visual Basic.NET

    Public Property MyProperty As Boolean

     Get

      Return Me.myvalue

     End Get

     Set

      Me.myvalue = value

     End Set

    End Property

    Пример пользовательского атрибута

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

    C#

    [AttributeUsage(AttributeTargets.All)]

    public class DeveloperAttribute : System.Attribute {

     // Закрытые поля.

     private string name;

     private string level;

     private bool reviewed;


     // Конструктор принимает два обязательных параметра: имя и уровень.

     public DeveloperAttribute(string name, string level) {

      this.name = name;

      this.level = level;

      this.reviewed = false;

     }

     // Свойство Name.

     // Только для чтения.

     public virtual string Name {

      get {

       return name;

      }

     }

     // Свойство Level.

     // Только для чтения.

     public virtual string Level {

      get {

       return level;

      }

     }

     // Свойство Reviewed.

     // Чтение / Запись.

     public virtual bool Reviewed {

      get {

       return reviewed;

      }

      set {

       reviewed = value;

      }

     }

    }

    MC++

    #using <mscorlib.dll>

    [AttributeUsage(AttributeTargets::All)]

    public __gc class DeveloperAttribute : public System::Attribute {

    private:

     // Закрытые поля.

     String* name;

     String* level;

     Boolean reviewed;

    public:

     // Конструктор принимает два обязательных параметра: имя и уровень.

     DeveloperAttribute(String* name, String* level) {

      this->name = name;

      this->level = level;

      this->reviewed = false;

     }

     // Свойство Name.

     // Только для чтения.

     __property virtual String* get_Name() { return name; }     

     // Свойство Level.

     // Только для чтения.

     __property virtual String* get_Level() { return level; }

     // Свойство Reviewed.

     // Чтение / Запись.

     __property virtual Boolean get_Reviewed() { return reviewed; }

     __property virtual void set_Reviewed(Boolean value) { reviewed = value; }

    }

    Visual Basic.NET

    Public Class <AttributeUsage(AttributeTargets.All)> DeveloperAttribute

     Inherits System.Attribute

     ' Закрытые поля.

     Private name As String

     Private level As String

     Private reviewed As Boolean

     ' Конструктор принимает два обязательных параметра: имя и уровень.

     Public Sub New(name As String, level As String)

      Me.name = name

      Me.level = level

      Me.reviewed = False

     End Sub

     ' Свойство Name.

     ' Только для чтения.

     Public Overridable ReadOnly Property Name() As String

      Get

       Return name

      End Get

     End Property

     ' Свойство Level.

     ' Только для чтения.

     Public Overridable ReadOnly Property Level() As String

      Get

       Return level

      End Get

     End Property

     ' Свойство Reviewed.

     ' Чтение / Запись.

     Public Overridable Property Reviewed() As Boolean

      Get

       Return reviewed

      End Get

      Set

       reviewed = value

      End Set

     End Property

    End Class

    Применять этот атрибут можно, используя как полное имя DeveloperAttribute, так и сокращенное – Developer:

    C#

    [Developer("Иван Семенов", "1")]

    [Developer("Иван Семенов", "1", Reviewed = true)]

    MC++

    [Developer(S"Иван Семенов", S"1")]

    [Developer(S"Иван Семенов", S"1", Reviewed = true)]

    Visual Basic.NET

    <Developer("Иван Семенов", "1")>

    <Developer("Иван Семенов", "1", Reviewed := True)>

    В первом примере показано применение атрибута с одним обязательным параметром, а во втором – с обоими типами параметров.

    Доступ к информации, хранящейся в атрибутах

    Теперь настало время рассмотреть механизм получения атрибутов, ведь мало научиться сохранять свои атрибуты в метаданных, важно еще и уметь получать и использовать их. К счастью, получение пользовательского атрибута – простая задача. Сначала объявляется переменная с типом атрибута, который нужно получить, затем она инициализируется с помощью вызова метода Attribute.GetCustomAttribute. Все, теперь можно использовать любые доступные свойства атрибута.

    Получение одиночного атрибута

    В следующем примере атрибут DeveloperAttribute (рассмотренный выше) применяется к классу MainApp в целом. Метод GetAttribute использует Attribyte.GetCustomAttribute для получения состояния атрибута DeveloperAttribute перед тем, как вывести информацию на консоль.

    C#

    using System;

    [Developer("Иван Семенов", "42", Reviewed = true)]

    class MainApp {

     public static void Main() {

      // Вызвать функцию получения и отображения атрибута.

      GetAttribute(typeof(MainApp));

     }

     public static void GetAttribute(Type t) {

      // Получить атрибут.

      DeveloperAttribute MyAttribute =

       (DeveloperAttribute)Attribute.GetCustomAttribute(t, typeof(DeveloperAttribute));

      if (MyAttribute == null) {

       Console.WriteLine("Атрибут не найден.");

      } else {

       // Получить поле Имя.

       Console.WriteLine("Имя: {0}." , MyAttribute.Name);

       // Получить поле Уровень.

       Console.WriteLine("Уровень: {0}." , MyAttribute.Level);

       // Получить поле Проверено.

       Console.WriteLine("Проверено: {0}." , MyAttribute.Reviewed);

      }

     }

    }

    MC++

    #using <mscorlib.dll> using namespace System;

    [Developer(S"Иван Семенов", S"42", Reviewed = true)]

    public__gc class MainApp{

    public:

     static void GetAttribute(Type* t) {

      // Получить атрибут.

      DeveloperAttribute* MyAttribute =

       __try_cast<DeveloperAttribute*>

       (Attribute::GetCustomAttribute(t, __typeof(DeveloperAttribute)));

      if (MyAttribute == 0)

       Console::WriteLine(S"Атрибут не найден.");

      else {

       // Получить поле Имя.

       Console::WriteLine(S"Имя: {0}." , MyAttribute->Name);

       // Получить поле Уровень.

       Console::WriteLine(S"Уровень: {0}." , MyAttribute->Level);

       // Получить поле Проверено.

       Console::WriteLine(S"Проверено: {0}." , MyAttribute->Reviewed);

      }

     }

    };


    void main() {

     // Вызвать функцию получения и отображения атрибута.

     MainApp::GetAttribute(__typeof(MainApp));

    }

    Visual Basic.NET

    Imports System

    Class <Developer("Иван Семенов", "42", Reviewed := True)> MainApp

     Public Shared Sub Main()

      ' Вызвать функцию получения и отображения атрибута.

      GetAttribute(GetType(MainApp))

     End Sub


     Public Shared Sub GetAttribute(t As Type) ' Получить атрибут.

      Dim MyAttribute As DeveloperAttribute = _

       CType(Attribute.GetCustomAttribute(t, GetType(DeveloperAttribute)), DeveloperAttribute)

      If MyAttribute Is Nothing Then Console.WriteLine("Атрибут не найден.")

      Else ' Получить поле Имя.

       Console.WriteLine("Имя: {0}.", MyAttribute.Name) ' Получить поле Уровень.

       Console.WriteLine("Уровень: {0}.", MyAttribute.Level) ' Получить поле Проверено.

       Console.WriteLine("Проверено: {0}.", MyAttribute.Reviewed)

      End If

     End Sub

    End Class

    При запуске эта программа выдает на консоль следующие строки:

    Имя: Иван Семенов

    Уровень: 42

    Проверено: True

    Если атрибут не найден, метод GetCustomAttribute возвращает нулевое значение. В этом примере предполагается, что атрибут определен в текущем пространстве имен, если это не так, не забудьте импортировать соответствующее пространство имен.

    Получение списка однотипных атрибутов

    В предыдущем примере ссылки на класс и атрибут передавались в метод GetCustomAttribute. Этот код прекрасно работает, если на уровне класса определен только один атрибут. Но если на том же уровне определено несколько однотипных атрибутов, этот метод вернет не всю информацию. В таких случаях нужно использовать метод Attribute.GetCustomAttributes, который возвращает массив атрибутов. Например, если на уровне класса определены два экземпляра атрибута DeveloperAttribute, можно модифицировать метод GetAttribute, чтобы получить оба.

    Как это сделать, показано в следующем примере:

    C#

    public static void GetAttribute(Type t) {

     // Получить атрибут.

     DeveloperAttribute[] MyAttribute =

      (DeveloperAttribute[]) Attribute.GetCustomAttributes(t, typeof(DeveloperAttribute));

     if (MyAttribute == null) Console.WriteLine("Атрибут не найден.");

     else for (int i = 0; i < MyAttribute.Length; i++) {

      // Получить поле Имя.

      Console.WriteLine("Имя: {0}." , MyAttribute[i].Name);

      // Получить поле Уровень.

      Console.WriteLine("Уровень: {0}." , MyAttribute[i].Level);

      // Получить поле Проверено.

      Console.WriteLine("Проверено: {0}.", MyAttribute[i].Reviewed);

     }

    }

    MC++

    public:

     static void GetAttribute(Type* t) {

      // Получить атрибут.

      DeveloperAttribute* MyAttribute __gc[] =

       __try_cast<DeveloperAttribute* __gc[]>(Attribute::GetCustomAttributes(t, __typeof(DeveloperAttribute)));

      if (MyAttribute == 0) Console::WriteLine(S"Атрибут не найден.");

      else for (int i = 0; i < MyAttribute.Length; i++) {

       // Получить поле Имя.

       Console::WriteLine(S"Имя: {0}." , MyAttribute[i]->Name);

       // Получить поле Уровень.

       Console::WriteLine(S"Уровень: {0}." , MyAttribute[i]->Level);

       // Получить поле Проверено.

       Console::WriteLine(S"Проверено: {0}." , MyAttribute[i]->Reviewed);

      }

     }

    Visual Basic.NET

    Public Shared Sub GetAttribute(t As Type)

     ' Получить атрибут.

     Dim MyAttribute As DeveloperAttribute() = _

      CType(Attribute.GetCustomAttributes(t, GetType(DeveloperAttribute)), DeveloperAttribute())

     If MyAttribute Is Nothing Then

      Console.WriteLine("Атрибут не найден.")

     Else

      Dim i As Integer

      For i = 0 To MyAttribute.Length – 1 ' Получить поле Имя.

       Console.WriteLine("Имя: {0}.", MyAttribute(i).Name)

       ' Получить поле Уровень.

       Console.WriteLine("Уровень: {0}.", MyAttribute(i).Level)

       ' Получить поле Проверено.

       Console.WriteLine("Проверено: {0}.", MyAttribute(i).Reviewed)

      Next i

     End If

    End Sub

    Получение списка разнотипных атрибутов

    Методы GetCustomAttribute и GetCustomAttributes не могут искать атрибут во всем классе и возвращать все его экземпляры. Они просматривают только один метод или поле за раз. Поэтому, если есть класс с одним атрибутом для всех методов и нужно получить все экземпляры этого атрибута, не остается ничего делать, как передавать эти методы один за другим в качестве параметров GetCustomAttribute и GetCustomAttributes.

    В следующем фрагменте кода показано, как получить все экземпляры атрибута DeveloperAttribute, определенного как на уровне класса, так и на уровне методов.

    C#

    using System;

    using System.Reflection;

    public static void GetAttribute(Type t) {

     // Получить атрибут уровня класса.

     DeveloperAttribute att =

      (DeveloperAttribute) Attribute.GetCustomAttribute (t, typeof(DeveloperAttribute));

     if (att == null)

      Console.WriteLine("Класс {0} не имеет атрибута Developer.\n", t.ToString());

     else {

      Console.WriteLine("Атрибут Имя на уровне класса: {0}.", att.Name);

      Console.WriteLine("Атрибут Уровень на уровне класса: {0}.", att.Level);

      Console.WriteLine("Атрибут Проверено на уровне класса: {0}.\n", att.Reviewed);

     }

     // Получить атрибуты уровня методов.

     // Получить все методы данного класса и поместить их

     // в массив объектов System.Reflection.MemberInfo.

     MemberInfo[] MyMemberInfo = t.GetMethods();

     // Вывести атрибуты всех методов класса

     for (int i = 0; i < MyMemberInfo.Length; i++) {

      att =

       (DeveloperAttribute)Attribute.GetCustomAttribute(MyMemberInfo[i], typeof (DeveloperAttribute));

      if (att == null)

       Console.WriteLine("Метод {0} не имеет атрибута Developer.\n" ,

        MyMemberInfo[i].ToString());

      else {

      Console.WriteLine("Атрибут Имя на уровне метода {0}: {1}.", MyMemberInfo[i].ToString(), att.Name);

       Console.WriteLine("Атрибут Уровень на уровне метода {0}: {1}.", MyMemberInfo[i].ToString(), att.Level);

       Console.WriteLine("Атрибут Проверено на уровне метода {0}: {1}.\n", MyMemberInfo[i].ToString(), att.Reviewed);

      }

     }

    }

    MC++

    using namespace System;

    using namespace System::Reflection;


    public:

     static void GetAttribute(Type* t) {

     // Получить атрибут уровня класса.

     DeveloperAttribute* att = __try_cast<DeveloperAttribute*>(Attribute::GetCustomAttribute(t, __typeof(DeveloperAttribute)));

     if (att == 0)

      Console::WriteLine(S"Класс {0} не имеет атрибута Developer.\n", t->ToString());

     else {

      Console::WriteLine(S"Атрибут Имя на уровне класса: {0}.", att->Name);

      Console::WriteLine(S"Атрибут Уровень на уровне класса: {0}.", att->Level);

      Console::WriteLine(S"Атрибут Проверено на уровне класса: {0}.\n", att->Reviewed);

     }

     // Получить атрибуты уровня методов.

     // Получить все методы данного класса и поместить их

     // в массив объектов System.Reflection.MemberInfo.

     MemberInfo* MyMemberInfo __gc[] = t->GetMethods();

     // Вывести атрибуты всех методов класса

     for (int i = 0; i < MyMemberInfo.Length; i++) {

      att =

      __try_cast<DeveloperAttribute*>(Attribute::GetCustomAttribute(MyMemberInfo[i], __typeof(DeveloperAttribute)));

      if (att == 0)

       Console::WriteLine(S"Метод {0} не имеет атрибута Developer.\n" , MyMemberInfo[i]->ToString());

       else {

        Console::WriteLine(S"Атрибут Имя на уровне метода {0}: {1}.", MyMemberInfo[i]->ToString(), att->Name);

        Console::WriteLine(S"Атрибут Уровень на уровне метода {0}: {1}.", MyMemberInfo[i]->ToString(), att->Level);

        Console::WriteLine(S"Атрибут Проверено на уровне метода {0}: {1}.\n", MyMemberInfo[i]->ToString(), att->Reviewed);

      }

     }

    }

    Visual Basic.NET

    Imports System

    Imports System.Reflection

    Public Shared Sub GetAttribute(t As Type)

     ' Получить атрибут уровня класса.

     Dim att As DeveloperAttribute = _

      ype(Attribute.GetCustomAttribute(t, GetType(DeveloperAttribute)), DeveloperAttribute)

     If att Is Nothing Then

      Console.WriteLine("Класс {0} не имеет атрибута Developer.", t.ToString())

     Else

      Console.WriteLine("Атрибут Имя на уровне класса: {0}.", att.Name)

      Console.WriteLine("Атрибут Уровень на уровне класса: {0}.", att.Level)

      Console.WriteLine("Атрибут Проверено на уровне класса: {0}.", att.Reviewed)

     End If

     ' Получить атрибуты уровня методов.

     ' Получить все методы данного класса и поместить их

     ' в массив объектов

     System.Reflection.MemberInfo.

     Dim MyMemberInfo As MemberInfo() = t.GetMethods()

     ' Вывести атрибуты всех методов класса

     Dim i As Integer

     For i = 0 To MyMemberInfo.Length – 1

      att =

       CType(Attribute.GetCustomAttribute(MyMemberInfo(i), GetType(DeveloperAttribute)), DeveloperAttribute)

      If att Is Nothing Then

       Console.WriteLine("Метод {0} не имеет атрибута Developer.", MyMemberInfo(i).ToString())

      Else

       Console.WriteLine("Атрибут Имя на уровне метода {0}: {1}.", MyMemberInfo(i).ToString(), att.Name)

       Console.WriteLine("Атрибут Уровень на уровне метода {0}: {1}.", MyMemberInfo(i).ToString(), att.Level)

       Console.WriteLine("Атрибут Проверено на уровне метода {0}: {1}.", MyMemberInfo(i).ToString(), att.Reviewed)

      End If

     Next i

    End Sub

    Для доступа к методам и полям проверяемого класса используются методы класса System::Type. В этом примере сначала через Type запрашивается информация об атрибутах, определенных на уровне класса, затем, через метод Type.GetMethods получается информация обо всех атрибутах, определенных на уровне методов. Эта информация помещается в массив объектов типа System.Reflection.MemberInfo. Если нужны атрибуты свойств, используется метод Type.GetProperties, а для конструкторов – Type.GetConstructors. Класс Type имеет множество методов для доступа к элементам типа, здесь описана только очень небольшая часть.

    Пример

    Демонстрационная программа, показывающая, как можно получать информацию из секций метаданных, написана на C#. На рисунке показана закладка, на которой можно увидеть значения атрибутов для сборки, на других закладках показывается аналогичная информация. Я не буду подробно описывать пример, кому надо, может скачать и скомпилировать его. Пример достаточно прост и разобраться в том, как получаются атрибуты, ни для кого не составит труда.

    Заключение

    Как видите, система метаданных в среде .NET достаточно хорошо продумана, и пользоваться ей легко и удобно.


    Это все на сегодня. Пока!

    (Алекс Jenter jenter@rsdn.ru) (Duisburg, 2001. Публикуемые в рассылке материалы принадлежат сайту RSDN.)







    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх