C# присвоить значения атрибутов по списку. Атрибуты

--- Сборки.NET --- Создание пользовательских атрибутов

В.NET Framework разработчику разрешено определять собственные атрибуты. Понятно, что эти атрибуты никак не отражаются на процессе компиляции, поскольку компилятор о них не осведомлен. Однако эти атрибуты, будучи применены к программным элементам, будут помещены в сборку в виде метаданных.

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

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

// Создаем атрибут public sealed class UInfoAttribute: System.Attribute { public string Desc; public UInfoAttribute() { } public UInfoAttribute(string str) { Desc = str; } }

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

По умолчанию действие специальных атрибутов может распространяться на практически любой аспект кода (методы, классы, свойства и т.д.). В одних случаях подобное поведение оказывается именно тем, что нужно, но в других, однако, может потребоваться создать вместо этого специальный атрибут, действие которого бы распространялось только на избранные элементы кода. Чтобы ограничить область действия специального атрибута, необходимо применить к его определению атрибут . Атрибут позволяет предоставлять (посредством операции OR) любую комбинацию значений из перечисления AttributeTargets:

//В этом перечислении описаны целевые объекты, //на которые распространяется действие атрибута. public enum AttributeTargets { All, Assembly, Class, Constructor, Delegate, Enum, Event, Field, GenericParameter, Interface, Method, Module, Parameter, Property, ReturnValue, Struct }

Более того, атрибут также позволяет дополнительно устанавливать свойство AllowMultiple , которое указывает, может ли атрибут применяться к одному и тому же элементу более одного раза (значением по умолчанию этого свойства является false). Помимо этого он также позволяет указывать, должен ли атрибут наследоваться производными классами, за счет применения именованного свойства Inherited (значением по умолчанию этого свойства является true).

Последнее обновление: 23.10.2018

Атрибуты в.NET представляют специальные инструменты, которые позволяют встраивать в сборку дополнительные метаданные. Атрибуты могут применяться как ко всему типу (классу, интерфейсу и т.д.), так и к отдельным его частям (методу, свойству и т.д.). Основу атрибутов составляет класс System.Attribute , от которого образованы все остальные классы атрибутов.

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

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

Public class AgeValidationAttribute: System.Attribute { public int Age { get; set; } public AgeValidationAttribute() { } public AgeValidationAttribute(int age) { Age = age; } }

По сути это обычный класс, унаследованный от System.Atribute. Теперь применим его к некоторому классу:

Public class User { public string Name { get; set; } public int Age { get; set; } public User(string n, int a) { Name = n; Age = a; } }

Пусть некоторый класс User применяет атрибут. Для этого имя атрибута указывается в квадратных скобках. Причем суффикс Attribute указывать необязательно. Обе записи и будут равноправны.

Если конструктор атрибута предусматривает использование параметров (public AgeValidationAttribute(int age)), то после имени атрибута мы можем указать значения для параметров конструктора. В данном случае передается значение для параметра age . То есть фактически мы говорим, что в AgeValidationAttribute свойство Age будет иметь значение 18.

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

Теперь получим с помощью рефлексии атрибут класса User и используем его для проверки объектов данного класса:

Class Program { static void Main(string args) { User tom = new User("Tom", 35); User bob = new User("Bob", 16); bool tomIsValid = ValidateUser(tom); // true bool bobIsValid = ValidateUser(bob); // false Console.WriteLine($"Реультат валидации Тома: {tomIsValid}"); Console.WriteLine($"Реультат валидации Боба: {bobIsValid}"); Console.ReadLine(); } static bool ValidateUser(User user) { Type t = typeof(User); object attrs = t.GetCustomAttributes(false); foreach (AgeValidationAttribute attr in attrs) { if (user.Age >= attr.Age) return true; else return false; } return true; } }

В данном случае в методе ValidateUser через параметр получаем некоторый объект User и с помощью метода GetCustomAttributes вытаскиваем из типа User все атрибуты. Далее берем из атрибутов атрибут AgeValidationAttribute при его наличии (ведь мы можем его и не применять к классу) и проверям допустимость возраста пользователя. Если пользователь прошел проверку по возрасту, то возвращаем true, иначе возвращаем false. Если атрибут не применяется, возвращаем true.

Ограничение применения атрибута

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

Public class RoleInfoAttribute: System.Attribute { //.................................... }

Ограничение задает перечисление AttributeTargets , которое может принимать еще ряд значений:

    All: используется всеми типами

    Assembly: атрибут применяется к сборке

    Constructor: атрибут применяется к конструктору

    Delegate: атрибут применяется к делегату

    Enum: применяется к перечислению

    Event: атрибут применяется к событию

    Field: применяется к полю типа

    Interface: атрибут применяется к интерфейсу

    Method: применяется к методу

    Property: применяется к свойству

    Struct: применяется к структуре

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

Практическое применение

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

По сути дела атрибут – это маркер, которым можно пометить элемент кода, например метод или класс (или даже отдельный аргумент).

[ Conditional] (рус. условный) используется для пометки метода как отладочного. Компилятор не будет компилировать этот метод (и любые операторы которые ссылаются на него), если определен следующий символ («DEBUG» например). Объявлен в System.Giagnostics.

Public void Generate() { // отладка }

[ DllImport] – помечает метод, как определенный во внешней DLL, а не в какой-либо сборке. Объявлен в пространстве имен System.Runtime.InteropServices.

Public static extern int MessageBox(int hParent, string Message, string Caption, int Type); ... MessageBox(0, "Hello", "myProgram", 0);

[ Obsolete(«сообщение»), false] – пометка метода, который считается устаревшим (генерируется либо предупреждение, либо ошибка компиляции).

[ StructLatout] – позволяет указать точное расположение полей структуры в памяти.

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

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

Public string SocialSecurityNumber() { // .. }

Увидев, что метод имеет атрибут FieldName, компилятор прибавит к этому имени строку «Attribute» (только в случае ее отсутствия), образовав полное имя FieldNameAttribute, а затем будет искать во всех пространствах имен класс с тем же именем. Компилятор предположит, что существует класс с таким именем и что этот класс наследуется от System.Attribute.

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

Public class FieldNameAttribute: Attribute { private string name; public FieldNameAttribute(string initName) { name = initName; } }

AttributeUsage – мета-атрибут, который служит для указания того, к каким элементам кода может быть применен атрибут. Атрибут может быть указан для сборки в целом (AttributeTargets.Assembly), при этом атрибут может быть помещен в любом месте кода, но должен быть отмечен с помощью ключевого слова: [assembly : SomeAssemblyAttribute(Parameters)]

AllowMultiple – может ли атрибут применяться более одного раза, (не обязательный).

Ingerited (рус. унаследованный) – атрибут примененный к классу или интерфейсу будет также применен ко всем унаследованным классам и интерфейсам? Если атрибут применен к методу или свойству, то он будет автоматически применен ко всем перекрытым методам или свойствам.

Принимает атрибут параметры или нет, определяется тем, какие конструкторы доступны.

Для добавления необязательных параметров («comment») можно организовать работу с помощью свойств или полей в классе атрибута. Для этого необходимо добавить свойство (pub. поле):

Private string comment; public string Comment { get { return comment; } set { comment = value; } }

Доступ осуществляется:

Атрибуты

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

Одной из задач компилятора.NET является генерирование описаний метаданных для всех типов, которые были определены, и на которые имеется ссылка в текущем проекте. Помимо стандартных метаданных, которые помещаются в любую сборку, программисты в.NET могут включать в состав сборки дополнительные метаданные с использованием атрибутов. В сущности, атрибуты представляют собой не более чем просто аннотации, которые могут добавляться в код и применяться к какому-то конкретному типу (классу, интерфейсу, структуре и т.д.), члену (свойству, методу и т.д.), сборке или модулю.

Концепция аннотирования кода с применением атрибутов является далеко не новой. Еще в COM IDL (Interface Definition Language - язык описания интерфейсов) поставлялось множество предопределенных атрибутов, которые позволяли разработчикам описывать типы, содержащиеся внутри того или иного СОМ-сервера. Однако в СОМ атрибуты представляли собой немногим более чем просто набор ключевых слов. Когда требовалось создать специальный атрибут, разработчик в СОМ мог делать это, но затем он должен был ссылаться на этот атрибут в коде с помощью 128-битного числа (GUID-идентификатора), что, как минимум, довольно затрудняло дело.

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

Ниже перечислены некоторые из предопределенных атрибутов, предлагаемые в различных пространствах имен.NET:

Заставляет элемент, к которому применяется, отвечать требованиям CLS (Common Language Specification - общеязыковая спецификация). Вспомните, что типы, которые отвечают требованиям CLS, могут без проблем использоваться во всех языках программирования.NET

Позволяет коду.NET отправлять вызовы в любую неуправляемую библиотеку кода на С или С++, в том числе и API-интерфейс лежащей в основе операционной системы. Обратите внимание, что при взаимодействии с программным обеспечением, работающим на базе СОМ, этот атрибут не применяется

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

Позволяет указать, что класс или структура является "сериализируемой", т.е. способна сохранять свое текущее состояние в потоке

Позволяет указать, что данное поле в классе или структуре не должно сохраняться в процессе сериализации

Позволяет указать, что метод может вызываться посредством HTTP-запросов, и CLR-среда должна преобразовывать его возвращаемое значение в формат XML

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

Потребители атрибутов

Как нетрудно догадаться, в составе NET Framework 4.0 SDK поставляется множество утилит, которые позволяют производить поиск разнообразных атрибутов. Даже сам компилятор C# (csc.ехе) запрограммирован так, что он проверяет наличие разных атрибутов во время компиляции. Например, сталкиваясь с атрибутом , он автоматически проверяет соответствующий элемент и удостоверяется в том, что в нем содержатся только отвечающие требованиям CLS инструкции, а при обнаружении элемента с атрибутом отображает внутри окна ErrorList (Список ошибок) в Visual Studio 2010 соответствующее предупреждение.

Помимо утилит, предназначенных для использования во время разработки, многие методы в библиотеках базовых классов.NET тоже изначально запрограммированы так, чтобы распознавать определенные атрибуты посредством рефлексии. Например, чтобы информация о состоянии объекта сохранялась в файле, все, что потребуется делать - это просто добавить в класс или структуру в виде аннотации атрибут . Встретив этот атрибут, метод Serialize() из класса BinaryFormatter автоматически сохраняет соответствующий объект в файле в компактном двоичном формате.

ГЛАВА 8. Атрибуты

Большинство языков программирования разрабатываются с учетом набора необходимых возможностей. Так, в начале создания компилятора вы думаете, какова будет структура приложений на новом языке, как один фрагмент кода будет вызывать другой, как распределить функциональность и о многих других проблемах, решение которых сделает язык продуктивным средством создания ПО. Обычно разработчикам компиляторов приходится иметь дело со статическими сущностями. Например, вы определяете класс на С#, помещая перед его именем ключевое слово class. После этого вы определяете производный класс, вставляя двоеточие после его имени, за которым следует имя базового класса. Это может служить примером решения, которое принимается разработчиком языка однажды и после этого не может быть изменено.

Сейчас те, кто пишет компиляторы, семь раз отмерят, прежде чем отрежут. Но даже они не могут предвидеть все будущие усовершенствования в нашей области и то, как они повлияют на способы выражения программистами типов на данном языке. Скажем, как создать связь между классом на C++ и URL документации для данного класса? Или как вы будете ассоциировать члены классов C++ с полями XML в новом решении вашей компании в области электронной коммерции? Поскольку C++ разрабатывался задолго до прихода в нашу жизнь Интернета и протоколов, таких как XML, обе эти задачи выполнить довольно трудно.

До сих пор решения подобных проблем предполагают хранение дополнительной информации в отдельном файле (DEF, IDL и т. д.), которая затем связывается с тем или иным типом или членом. Так как компилятор не обладает сведениями о каком-то файле или связи между вашим классом и этим файлом, такой подход обычно называется "разрывным решением" (disconnected solution). Главная проблема в том, что класс больше не является "самоописывающимся", т. е. теперь пользователь не может сказать о классе все, лишь взглянув на определение класса. Одно из преимуществ самоописывающегося компонента - гарантия соблюдения при компиляции и в период выполнения правил, ассоциированных с компонентом. Кроме того, сопровождение самоописывающегося компонента проще, поскольку разработчик может найти всю связанную с ним информацию в одном месте.

Так все и было многие десятилетия. Разработчики языка пытаются определить, что вы хотите от языка, и создают компилятор с этими возможностями, и, к счастью или к сожалению, вы остаетесь с этими возможностями до прихода следующего компилятора. Так это и есть вплоть до сегодняшнего дня. С# предлагает иную парадигму, берущую начало от атрибутов (attributes).

Что такое атрибуты

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

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

class MyClass {

Public int Foo; }

Чтобы прикрепить определенный атрибут к типу или члену С#, нужно просто задать данные атрибута в скобках перед целевым типом или членом. В нашем примере мы прикрепили атрибут RegistryKey к полю MyClass.Foo. Как вы вскоре увидите, все, что нам надо сделать в период выполнения, - это запросить значение поля, связанное с разделом реестра и использовать его, чтобы сохранить дату в реестре.

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

В предыдущем примере синтаксис прикрепления атрибута к типу или члену похож на тот, что применяется при создании экземпляра класса. Дело в том, что атрибут на самом деле является классом, производным от базового класса System.Attribute.

А теперь немного расширим атрибут RegistryKey:

public enun RegistryHives {

HKEY_CLASSES_ROOT,

HKEY_CURRENT_USER.

HKEY_LOCAL_MACHINE,

HKEY.USERS,

HKEY_CURRENT_CONFIG }

{

{

this.Hive = Hive; this.ValueName = ValueNane; >

get { return hive; }

set { hive = value; } >

protected String valueNane; public String ValueName

<

get { return valueName; >

set { valueName = value; } } >

В этом примере я добавил епит для различных типов разделов реестра, конструктор для класса-атрибута (который принимает тип и имя раздела реестра) и два свойства для имени улья реестра и имени значения. Теперь мы видим более широкие возможности атрибутов, так что посмотрим, как запросить атрибут в период выполнения. Мы будем работать с полностью функциональным примером. Перейдем к рассмотрению некоторых более сложных вопросов, связанных с определением и прикреплением атрибутов.

ПРИМЕЧАНИЕ В приведенных примерах к именам классов атрибутов добавлено слово Attribute. Однако, прикрепляя атрибут к типу или члену, я отбросил этот суффикс. Это одна из возможностей сокращения, встроенная разработчиками С#, и она дается нам даром. Обнаружив атрибут, прикрепленный к типу или члену, компилятор будет искать класс с именем заданного атрибута, производный от System.Att-ribute. Если класс найти не удастся, компилятор добавит к имени заданного атрибута слово Attribute и будет продолжать поиск получившегося имени. Поэтому в повседневной практике имена классов атрибутов при определении оканчиваются словом Attribute. Впоследствии эта часть имени опускается.

Запрос атрибутов

Мы знаем, как определять атрибуты В виде производных от System.Attri-bute и как прикреплять их к типу или члену. А что теперь? Как использовать атрибуты при программировании? Короче, как производится запрос типа или члена как прикрепленных к ним атрибутов (и их параметров)?

Чтобы запросить тип или член о прикрепленных к ним атрибутах, нужно применить отражение (reflection). Об отражении речь пойдет в главе 16, поэтому здесь я коснусь его лишь в той мере, в какой это необходимо, чтобы проиллюстрировать получение информации об атрибутах в период выполнения.

Отражение - это функция, позволяющая в период выполнения динамически определять характеристики типов в приложении. Например, с помощью Reflection API из состава.NET Framework можно циклически запрашивать метаданные всего приложения и создавать списки определенных для него классов, типов и методов. Рассмотрим несколько примеров атрибутов и способов их запроса с помощью отражения.

Атрибуты класса

Способ получения атрибута зависит от типа члена, к которому производится запрос. Допустим, вам нужно узнать атрибут, определяющий удаленный сервер, на котором должен быть создан объект. Без атрибутов вам бы пришлось сохранять эту информацию в константе или файле ресурсов приложения. А используя их, можно просто создать аннотацию для класса с именем удаленного сервера, например, так:

using System;

public enum RemoteServers

{

JEANVALJEAN,

JAVERT,

COSETTE }

public class RemoteObjectAttribute: Attribute

{

public RemoteObjectAttribute(RemoteServers Server)

this, server = Server;

}

protected RemoteServers server;

public string Server

<

get

{

return RemoteServers.GetName(typeof(RemoteServers),

this, server);

t RemoteObj ect(RemoteSe rve rs.COSETTE)]

class MyRemotableClass

{

}

Сервер, на котором нужно создавать объект, можно определить так:

class ClassAttrApp {

public static void Main() {

Type type = typeof(MyReraotableClass);

foreach (Attribute attr in type.GetCustomAttributesQ)

RemoteObjectAttribute remoteAttr = attr as RemoteObjectAttribute; if (null != remoteAttr) {

Console.WriteLine("Co3flai*Te этот объект на {0}.",

remoteAttr. Server); } } } }

Как можно ожидать, приложение выдаст следующее: Создайте этот объект на COSETTE.

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

Первая строка в методе Main использует оператор typeof:

Type type = typeof(MyRemotableClass);

Этот оператор возвращает ассоциированный с типом объект System. Туре, который передается как его единственный аргумент. Как только этот объект оказался в вашем распоряжении, к нему можно выполнить запрос.

Относительно следующей строки в пояснении нуждаются два момента:

foreach (Attribute attr in type.GetCustomAttributes(true))

Первый - вызов метода Ту ре.GetCustomAttributes. Этот метод возвращает массив значений типа Attribute, который в этом случае будет содержать все атрибуты, прикрепленные к классу MyRemotableClass. Второй - оператор foreach, циклически обрабатывающий возвращенный массив, помещая каждое последовательное значение в переменную attr типа Attribute.

Следующее выражение использует оператор as, чтобы попытаться преобразовать переменную attr в тип RemoteObjectAttribte:

RemoteObjectAttribute remoteAttr =

attr as RemoteObjectAttribute;

Далее выполняется проверка на пустое значение, которое указывает на наличие сбоя при использовании оператора as. Если значение не пустое, значит, переменная remoteAttr содержит верный атрибут, прикрепленный к типу MyRemotableClass - мы вызываем одно из свойств RemoteObjectAttribute, чтобы вывести имя удаленного сервера:

if (null != remoteAttr) <

Console.WriteLine("Создайте этот объект на {0}",

remoteAttr. Server); }

Атрибуты метода

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

using System;

using System.Reflection;

public class TransactionableAttribute: Attribute {

public TransactionableAttributeO

{

> }

class TestClass {


public void Foo()

{>

public void Bar() {}

Public void Baz() {} >

class MethodAttrApp

public static void Main() {

Type type = Type.GetType("TestClass"); foreach(MethodInfo method in type.GetMethodsQ) {

foreach (Attribute attr in

method.GetCustomAtt ributes()) {

Console.WriteLine("{0} может участвовать " +

"в транзакции.", method.Name); } } } > }

Этот код выводит следующую информацию:

Foo может участвовать в транзакции. Baz может участвовать в транзакции.

В данном примере достаточно простого присутствия TransactionableAttribute, чтобы сообщить коду о том, что метод, для которого указан этот атрибут, может принимать участие в транзакции. Поэтому не определено ни одного его члена, кроме "голого" конструктора без параметров.

TestClass определен с тремя методами (Foo, Bar и Baz), два из которых определены как способные принимать участие в транзакциях. Заметьте: для прикрепления атрибута с конструктором, не принимающим параметров, открывающую и закрывающую скобки вводить не требуется.

Дальше еще интересней. Давайте приглядимся к способу запроса методов класса как их атрибутов. Начнем мы с использования GetType, статического метода Туре, для получения объекта System. Type класса TestClass:

Type type = Type.GetType("TestClass");

Затем мы вызовем метод Type.GetMethods, чтоб получить массив объектов Methodlnfo. Каждый из этих объектов содержит данные о методе класса TestClass. Мы обработаем каждый метод оператором foreach:

foreach(Method!nfo method in type.GetMethodsO)

Теперь, имея объект Methodlnfo, мы можем, вызвав метод Methodlnfo.-GetCustomAttributes, получить все атрибуты метода, созданные пользователем. Мы снова произведем циклическую обработку возвращенного массива объектов оператором foreach:

foreach (Attribute attr in method.GetCustomAttributes(true))

К данному моменту выполнения программы у нас уже есть атрибут метода. Теперь с помощью оператора is мы сделаем к нему запрос, является ли он атрибутом TransactionableAttribute. Если это так, мы выводим имя метода:

if (attr is TransactionableAttribute) {

Console.Writel_ine("{0} может участвовать в транзакции.",

method.Name); }

Атрибуты поля

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

using System;

using System.Reflection;

public enum RegistryHives {

HKEY_CLASSES_ROOT,

HKEY_CURRENT_USER,

HKEY_LOCAL_MACHINE,

HKEYJJSERS,

HKEY_CURRENT_CONFIG >

public class RegistryKeyAttribute: Attribute <

public RegistryKeyAttribute(RegistryHives Hive, String ValueName)

{

this.Hive = Hive; this.ValueName = ValueName; }

protected RegistryHives hive; public RegistryHives Hive {

get { return hive; }

set { hive = value; } >

protected String valueName; public String ValueName {

get { return valueName; }

set { valueName = value; } } }

class TestClass <


public int Foo;

public int Bar; }

class FieldAttrApp {

public static void Main() {

Type type = Type.GetTypeC"TestClass"); foreach(Field!nfo field in type.GetFieldsQ) {

foreach (Attribute attr in field.GetCustomAttributesO) {

F.agistryKeyAttribute registryKeyAttr =

attr as RegistryKeyAttribute; if (null != registryKeyAttr) {

Console.WriteLine

("{0} будет сохранен в Ш\\\\{2}", field.Name,

registryKeyAttr.Hive, registryKeyAttr.ValueName); } } } } }

Я не буду описывать выполнение каждого этапа этого кода, так как он в чем-то дублирует предыдущий пример. Однако пара деталей все же важна для нас. Во-первых, как и объект Methodlnfo, определенный для получения информации о методе из объекта типа, объект Fieldlnfo предоставляет аналогичную функциональность для получения из объекта сведений о поле. Как и в предыдущем примере, мы начнем с получения объекта типа, ассоциированного с нашим тестовым классом. Затем мы циклически обработаем массив Fieldlnfo, а также все атрибуты каждого объекта Fieldlnfo, пока не найдем нужный - RegistryKeyAttribute. Если мы его обнаружим, то выведем имя поля и значения полей атрибута Hive и ValueName.

Параметры атрибута

В вышеприведенном примере я рассказал об использовании прикрепленных атрибутов с помощью их конструкторов. А теперь рассмотрим некоторые вопросы, связанные с конструкторами атрибутов, которых я не касался раньше.

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

В примере FieldAttrApp (см. выше) вы могли видеть атрибут с именем RegistryKeyAttribute. Его конструктор имел такой вид:

public RegistryKeyAttribute(RegistryHives Hive, String ValueName)

Public int Foo;

Пока все просто. У конструктора есть два параметра, и два параметра использовались, чтобы прикрепить этот атрибут к полю. Однако мы можем упростить код. Если параметр большую часть времени останется неизменным, зачем каждый раз заставлять пользователя класса типизировать его? Мы можем установить значения по умолчанию, применив позиционные и именованные параметры.

Позиционными называются параметры конструктора атрибута. Они обязательны и должны задаваться каждый раз при использовании атрибута. В нашем примере Registry Key Attribute позиционными являются оба параметра, Hive и ValueName. Именованные параметры на самом деле не определяются в конструкторе атрибута. Они скорее представляют собой нестатические поля и свойства. Поэтому именованные параметры позволяют клиенту устанавливать значения полей и свойств атрибута при создании его экземпляра, не требуя от вас создания конструктора для каждой возможной комбинации полей и свойств, значения которых может понадобиться установить клиенту.

Каждый открытый конструктор может определять последовательность позиционных параметров. Это верно и в отношении любого типа класса. Но в случае атрибутов после указания позиционных параметров пользователь может ссылаться на некоторые поля или свойства, применяя синтаксис Имя_поля_или_свойства=3начение. Чтобы проиллюстрировать это, изменим атрибут Registry Key Attribute. Мы создадим лишь один позиционный параметр RegistryKeyAttribute. ValueName, а необязательным именованным параметром будет Registry KeyAttribute. Hive. Итак, возникает вопрос: "Как определить что-либо как именованный параметр?" Поскольку в определение конструктора включены только позиционные - и поэтому необходимые - параметры, просто удалите параметр из определения конструктора. Впоследствии пользователь может указывать как именованный параметр любое поле, не являющееся readonly, static или const, или любое поле, у которого есть метод-аксессор для установки его значения, или установщик, который не является статическим. Поэтому чтобы сделать Registry Key Attribute. Hive именованным параметром, мы уберем его из определения конструктора, так как он уже существует в виде открытого свойства, доступного для чтения и записи:

Теперь пользователь может прикрепить атрибут любым из следующих способов:

Это дает вам гибкость, обеспечиваемую наличием у поля значения по умолчанию, в то же время предоставляя пользователю возможность изменять это значение при необходимости. Секунду! Если пользователь не устанавливает значения поля Registry Key Attribute. Hive, как мы установим для него значение по умолчанию? Вы можете подумать: "Хорошо, посмотрим, не установлено ли оно в конструкторе". Однако проблема в том, что Registry KeyAttribute. Hive - это епит, в основе которого лежит int - размерный тип. Это значит, что по умолчанию компилятор инициализирует его значением 0! Если мы изучим значение RegistryKey-Attribute.Hive в конструкторе и найдем его равным 0, мы не сможем узнать, установлено ли оно вызывающим кодом через именованный параметр или инициализировано компилятором как размерный тип. К сожалению, единственный известный мне пока способ решения этой проблемы - это изменить код так, чтобы значение, равное 0, стало неверным. Это можно сделать, изменив RegistryHives епит:

public enum RegistryHives {

HKEY_CLASSES_ROOT = 1,

HKEY_CURRENT_USER,

HKEY_LOCAL_MACHINE,

HKEY_USERS,

HKEY_CURRENT_CONFIG }

Теперь мы знаем, что единственный способ, позволяющий Registry-Key Attribute. Hive быть равным 0, - инициализация его компилятором этим значением, если после этого пользователь не изменил его значение через именованный параметр. Сейчас мы можем написать для инициализации код вроде этого:

public RegistryKeyAttribute(String ValueName) <

if (this.Hive == 0)

this.Hive = RegistryHives.HKEY_CURRENT_USER;

this.ValueName = ValueName; }

Распространенные ошибки при использовании именованных параметров

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

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

Кроме того, вы не можете именовать позиционные параметры. При компиляции атрибутов компилятор сначала попытается разрешить именованные параметры, затем разрешить оставшиеся - именованные - параметры с помощью сигнатуры метода. Хотя компилятор сможет разрешить каждый именованный параметр, следующий код не будет компилироваться, так как по завершении разрешения именованных параметров компилятор не найдет ни одного позиционного параметра и выдаст сообщение "No overload for method "Registry Key Attribute" takes "0" arguments"".

Наконец, именованными параметрами могут быть любые открытые поля или свойства, включая метод-установщик, не определенные как static или const.

Допустимые типы параметров атрибутов

Типы позиционных и именованных параметров класса атрибута ограничены следующим набором:

  • bool, byte, char, double, float, int, long, short, string;
  • System. Type",
  • object;
  • enum - при условии, что он и все типы, по отношению к которым он является вложенным, открытые (как в примере, где используется перечисление ульев реестра);
  • одномерный массив значений любого из вышеуказанных типов.

Поскольку набор типов параметров ограничен приведенным выше списком, вы не можете передавать конструктору структуры данных наподобие класса. Это ограничение имеет смысл, так как атрибуты прикрепляются в период разработки и в это время у вас не будет созданного экземпляра класса (объекта). Допускается применение вышеуказанных типов, так как они позволяют жестко запрограммировать их значения во время разработки.

Атрибут AttributeUsage

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

Public class RemoteObjectAttribute: Attribute] {

}


public class TransactionableAttribute: Attribute

{

}


public class RegistryKeyAttribute: Attribute

<

}

И последний момент относительно перечисления AttributeTargets: вы можете комбинировать члены с помощью оператора |. Если у вас есть атрибут, применимый и к полям, и к свойствам, Attribute Usage можно прикрепить так:

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

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

using System;

using System.Reflection;

public class SingleUseAttribute: Attribute {

// ОШИБКА: возникает ошибка компилятора "duplicate attribute".



class MyClass

{

}

class SinglellseApp {

public static void Main()

{

} }

Чтобы исправить эту ошибку, укажите в строке Attribute Usage, что вы хотите разрешить многократное использование атрибута с данным типом. Этот код будет работать:

using System;

using System.Reflection;


public class SingleUseAttribute: Attribute

{

public SingleUseAttribute(String str)

class MyClass

{

>

class MultiUseApp {

public static void Main()

{ } }

Практическим примером использования этого подхода служит атрибут RegistryKeyAttribute (см. раздел "Определение атрибутов"). Поскольку вполне реально, что поле может быть сохранено в нескольких местах реестра, можете прикрепить атрибут Attribute Usage с именованным параметром AllowMultiple, как показано в примере выше.

Задание правил наследования атрибутов

Последний параметр атрибута Attribute Usage - флаг inherited - определяет, может ли атрибут наследоваться. Его значение по умолчанию равно false. Если же установить флаг inherited в true, его значение зависит от значения флага AllowMultiple. Если inherited установлен в true, a Allow-Multiple - в false, установленный атрибут заменит унаследованный. Однако если и inherited, и AllowMultiple установлен в true, атрибуты члена будут аккумулироваться.

Идентификаторы атрибутов

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

class MyClass {


public long Foo(); }

Если у вас есть опыт работы с СОМ, вы должны знать, что HRESULT - это стандартный возвращаемый тип для всех методов, кроме AddRef или Release. Однако нетрудно заметить, что если имя атрибута применимо и к возвращаемому значению, и к имени метода, то компилятору будут непонятны ваши намерения. Вот несколько сценариев, -в которых компилятор не поймет ваших намерений из контекста:

  • метод или возвращаемый тип;
  • событие, поле или свойство;
  • делегат или возвращаемый тип;
  • свойство, аксессор, возвращаемое значение метода-получателя или параметр значения установщика.

В каждом из этих случаев компилятор производит определение на основе того, что считается "наиболее употребительным". Чтобы обойти такой путь принятия решения, используйте идентификаторы атрибута: assembly, module, type, method, property, event, field, param, return.

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

class MyClass {


public long Foo(); }

Подведем итоги

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