Что такое атрибут? Для каких целей используются атрибуты?

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

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

class AgeValidationAttribute : Attribute
{
    public int Age { get;}
    public AgeValidationAttribute() { }
    public AgeValidationAttribute(int age) => Age = age;
}

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

Теперь применим его к некоторому классу:

[AgeValidation(18)]
public class Person
{
    public string Name { get;}
    public int Age { get; set; }
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

Данный класс Person применяет атрибут. Для этого имя атрибута указывается в квадратных скобках непосредственно перед определением класса. Причем суффикс Attribute указывать необязательно. Обе записи [AgeValidation(18)] и [AgeValidationAttribute(18)] будут равноправны.

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

В качестве альтернативы можно использовать именованные параметры для всех свойств атрибута, если класс атрибута имеет конструктор без параметров: [AgeValidation(Age = 18)]

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

Person tom = new Person("Tom", 35);
Person bob = new Person("Bob", 16);
bool tomIsValid = ValidateUser(tom);    // true
bool bobIsValid = ValidateUser(bob);    // false
 
Console.WriteLine($"Результат валидации Тома: {tomIsValid}");
Console.WriteLine($"Результат валидации Боба: {bobIsValid}");
 
bool ValidateUser(Person person)
{
    Type type = typeof(Person);
    // получаем все атрибуты класса Person
    object[] attributes = type.GetCustomAttributes(false);
 
    // проходим по всем атрибутам
    foreach (Attribute attr in attributes)
    {
        // если атрибут представляет тип AgeValidationAttribute
        if (attr is AgeValidationAttribute ageAttribute)
            // возвращаем результат проверки по возрасту
            return person.Age >= ageAttribute.Age;
    }
    return true;
}
 
class AgeValidationAttribute : Attribute
{
    public int Age { get;}
    public AgeValidationAttribute() { }
    public AgeValidationAttribute(int age) => Age = age;
}
 
[AgeValidation(18)]
public class Person
{
    public string Name { get;}
    public int Age { get; set; }
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

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

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

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

[AttributeUsage(AttributeTargets.Class)]
class AgeValidationAttribute : Attribute
{
//....................................
}

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

  • All: используется всеми типами
  • Assembly: атрибут применяется к сборке
  • Constructor: атрибут применяется к конструктору
  • Delegate: атрибут применяется к делегату
  • Enum: применяется к перечислению
  • Event: атрибут применяется к событию
  • Field: применяется к полю типа
  • Interface: атрибут применяется к интерфейсу
  • Method: применяется к методу
  • Property: применяется к свойству
  • Struct: применяется к структуре

С помощью логической операции ИЛИ можно комбинировать эти значения. Например, пусть атрибут может применяться к классам и структурам: [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *