C# .NET. Конструкторы в классах атрибутов

Конструкторы в классах атрибутов. Позиционные и именованные параметры. Примеры

Перед изучением данной темы рекомендуется ознакомиться со следующей темой:


Содержание


Поиск на других ресурсах:




1. Конструкторы в классах атрибутов. Особенности применения. Пример

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

Например, если класс атрибута имеет конструктор с параметрами типа string и int

// Некоторый класс атрибута
class MyClassCommentAttribute : Attribute
{
  // Внутренние поля
  string field1;
  int field2;

  // Конструктор с параметрами типа string, int
  public MyClassCommentAttribute(string _param1, int _param2)
  {
    // ...
  }
}

то при присоединении этого класса к другому классу нужно придерживаться правильного порядка и сигнатуры в параметрах

...

[MyClassComment("Some comment", 5)]
class MyClass
{
  // ...
}

...

В вышеприведенном коде в строке

[MyClassComment("Some comment", 5)]

вызывается конструктор класса MyClassCommentAttribute. Конструктор получает первым параметром строку «Some comment», вторым параметром целочисленное значение 5. Если попытаться изменить количество или типы значений, передаваемых в конструктор, возникнет ошибка на этапе компиляции.

 

2. Позиционные и именованные параметры. Определение. Пример

В конструкторах классов-атрибутов различают два вида параметров:

  • позиционные параметры. В этих параметрах аргумент связывается с параметром в соответствии с его позицией в списке аргументов. Если в конструктор класса-атрибута передается несколько позиционных аргументов, то первый аргумент связывается с первым параметром, второй аргумент связывается со вторым параметром и т.д. Как правило, позиционные параметры инициализируют внутренние поля класса-атрибута;
  • именованные параметры. Это случай, когда параметру присваивается начальное значение по его имени. В этом случае важно имя параметра, а не его позиция. Именованными параметрами могут быть только общедоступные (public) поля или свойства класса-атрибута.

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

class NameOfClassAttribute : Attribute
{
  ...

  // Конструктор, в котором параметры передаются по их позициям
  public NameOfClassAttribute(
    type1 pos_parameter1,
    type2 pos_parameter2,
    ...
    typeN pos_parameterN)
  {
    ...
  }

  ...
}

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

[NameOfClassAttribute(
  pos_parameter1,
  pos_parameter2,
  ...
  pos_parameterN,
  named_parameter_1 = value1,
  named_parameter_2 = value2,
  ...
  named_parameter_N = valueN)]
class SomeClass
{
  ...
}

здесь

  • NameOfClassAttribute — класс атрибута, который присоединяется к другому классу с именем SomeClass;
  • pos_parameter1, pos_parameter2, pos_parameterN — имена позиционных параметров;
  • name_parameter1, named_parameter2, named_parameterN — имена именованных параметров, которым присваиваются соответственно значения value1, value2, valueN.

 

3. Пример использования именованного параметра

Пусть задан класс Car, описывающий информацию об автомобиле. К классу присоединяется класс-атрибут CommentAttribute, который добавляет к классу некоторое объяснение (комментарий).

Реализация класса Car содержит следующие элементы:

  • внутренние поля model и year, которые определяют марку автомобиля и год выпуска;
  • конструктор с двумя параметрами;
  • свойства Model и Year, которые возвращают значения внутренних полей.

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

  • общедоступное (public) поле comment, которое содержит комментарий. При присоединении к классу Car, это поле будет задаваться как именованный параметр;
  • конструктор без параметров. В теле конструктора инициализируется поле comment значением по умолчанию.

Класс CommentAttribute может быть присоединен к любым другим классам.
В функции main() с помощью рефлексии выводится информация о присоединенных к классу Car атрибутах.

Текст демонстрационной программы следующий:

using System;
using System.Reflection;
 
namespace ConsoleApp18
{
  // 1. Класс-атрибут, определяющий общедоступный комментарий
  [AttributeUsage(AttributeTargets.Class)] // использовать только для классов
  class CommentAttribute : Attribute
  {
    public string comment;

    // Конструктор без параметров
    public CommentAttribute()
    {
      comment = "No comments."; // значение по умолчанию
    }
  }

  // 2. Класс, описывающий данные об автомобиле
  // К классу присоединяется класс CarAttribute со значением комментария "This is a car".
  [Comment(comment = "This is a car!")] // здесь comment - именованный параметр
  class Car
  {
    // Внутренние поля класса
    private string model;
    private int year;

    // Конструктор
    public Car(string _model, int _year)
    {
      model = _model;
      year = _year;
    }

    // Свойства доступа к полям
    public string Model
    {
      get { return model; }
    }

    public int Year
    {
      get { return year; }
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Демонстрация использования
      // 1. Создать экземпляр класса Car
      Car car = new Car("Renault", 2008);

      // 2. Вывести информацию о присоединенном атрибуте CommentAttribute
      object[] attrs = Attribute.GetCustomAttributes(car.GetType());

      foreach (object at in attrs)
      {
        // 2.1. Вывести имя присоединенного атрибута на основании экземпляра at
        Console.WriteLine("Attribute name: " + at.GetType().Name);

        // 2.2. Вывести значение комментария, который присоединен к классу Car
        // 2.2.1. Получить экземпляр типа Type
        Type tp = at.GetType();

        // 2.2.2. Получить информацию о поле comment
        FieldInfo fi = tp.GetField("comment");

        // 2.2.3. Получить значение поля comment
        string comment = (string)fi.GetValue(at);

        // 2.2.4. Вывести comment на экран
        Console.WriteLine("comment = " + comment); // This is a car!
      }

      Console.ReadKey();
    }
  }
}

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

Attribute name: CommentAttribute
comment = This is a car!

Объясним некоторые фрагменты кода. Класс-атрибут создается в строках

...

[AttributeUsage(AttributeTargets.Class)] // использовать только для классов
class CommentAttribute : Attribute
{
  ...
}

...

Как видно из вышеприведенного кода, класс унаследован от встроенного класса AttributeUsage. Этот класс позволяет ограничить применение пользовательских классов-атрибутов для определенных элементов. В нашем случае в конструкторе класса AttributeUsage указывается значение AttributeTargets.Class. Это означает, что наш класс CommentAttribute может быть присоединен только к классам. К другим элементам (структурам, перечислениям, методам и т.д.) он не может быть присоединен.

Присоединение класса-атрибута CommentAttribute к классу Car осуществляется в строках

...

[Comment(comment = "This is a car!")] // здесь comment - именованный параметр
class Car
{
  ...
}

...

Как видно из строк выше, не обязательно указывать полное имя класса CommentAttribute. Достаточно указать только имя Comment. Однако, если указать CommentAttribute, то ошибки не будет.
При присоединении, вызывается конструктор класса CommentAttribute. В данном случае, задается конкретное значение именуемого параметра comment

Comment(comment = "This is a car!")

Это значит, что класс CommentAttribute присоединяется к классу автомобиля.

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

[Comment] // не указан именованный параметр
class Car
{
  ...
}

або

[Comment()]
class Car
{
  ...
}

В этом случае внутреннее поле comment будет инициализировано значением по умолчанию «No comments.», которое задается в конструкторе класса CommentAttribute

...

class CommentAttribute : Attribute
{
  ...

  // Конструктор без параметров
  public CommentAttribute()
  {
    comment = "No comments."; // значение по умолчанию
  }
}

 

4. Пример использования позиционных и именованных параметров в классе-атрибуте. Конструктор получает два параметра

В примере объявляется класс с именем SomeClassAttribute. В классе объявляются следующие элементы:

  • внутренние скрытые (private) поля с именами fInt и fDouble;
  • общедоступные (public) поля с именами fString и fFloat;
  • конструктор, который получает два позиционных параметра.

С целью демонстрации класс SomeClassAttribute присоединяется к четырем классам с именами Class1, Class2, Class3, Class4 различными способами:

  • без указания именуемых параметров (Class1);
  • с указанием одного именованного параметра (Class2, Class3);
  • с указанием двух именуемых параметров (Class4).

 

...

// Некоторый класс, который содержит конструктор,
// получающий два позиционных параметра
class SomeClassAttribute : Attribute
{
  // Внутренние скрытые поля класса
  private int fInt;
  private double fDouble;

  // Общедоступные поля класса
  public string fString;
  public float fFloat;

  // Конструктор, получающий два позиционных параметра
  public SomeClassAttribute(
    int param1,
    double param2)
  {
    // Инициализация скрытых полей значениями позиционных параметров
    fInt = param1;
    fDouble = param2;

    // Инициализация общедоступных полей в конструкторе
    // значениями по умолчанию
    fString = "None";
    fFloat = 1.0f;
  }
}

// Присоединение класса NameOfClassAttribute к другому классу.
// Случай 1 - не указываются именованные параметры
[SomeClass(25, 8.83)]
class Class1
{
  // ...
}

// Случай 2 - указывается только один именованный параметр типа string
[SomeClassAttribute(3, 7.55, fString = "SomeClass2")]
class Class2
{
  // ...
}

// Случай 3 - указывается только один именованный параметр типа float
[SomeClass(8, 1.25, fFloat = 9.5f)]
class Class3
{
  // ...
}

// Случай 4 - указываются два именованных параметра
[SomeClass(1, -10.5, fFloat = 1.8f, fString = "SomeClass3")]
class Class4
{
  // ...
}

...

 

5. Применение атрибута к методу. Пример

Атрибуты могут присоединяться к методам класса. Ниже приведен пример присоединения атрибута VersionAttribute к методу Print() класса CharArray. Атрибут определяет номер версии для метода Print().

В функции main() выводятся данные о присоединенном атрибуте:

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

 

using System;
using System.Reflection;

namespace ConsoleApp18
{
  // 1. Объявить класс, который есть атрибутом.
  // Класс описывает строку, определяющую версию элемента.
  // Этот класс-атрибут используется только для метода.
  [AttributeUsage(AttributeTargets.Method)]
  class VersionAttribute : Attribute
  {
    // 1.1. Общедоступное поле - версия метода
    public string version;

    // 1.2. Конструктор без параметров
    public VersionAttribute()
    {
      version = "None"; // значение по умолчанию
    }

    // 1.3. Свойство для доступа к версии
    public string Version
    {
      get { return version; }
    }
  }

  // 2. Класс, описывающий массив символов типа char[].
  // В классе есть метод Print(), к которому присоединяется атрибут VersionAttribute
  class CharArray
  {
    // 2.1. Внутреннее поле
    private char[] ca;

    // 2.2. Конструктор
    public CharArray(char[] _ca)
    {
      ca = _ca;
    }

    // 2.3. Метод Print(), к которому присоединяется атрибут VersionAttribute
    [Version(version = "1.0")] // version - именованный параметр
    public void Print()
    {
      Console.WriteLine("Array CA:");
      for (int i = 0; i < ca.Length; i++)
        Console.Write(ca[i] + " ");
      Console.WriteLine();
    }

    // 2.4. Свойство доступа к полю ca
    public char[] Value
    {
      get { return ca; }
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // 1. Использование класса CharArray
      // 1.1. Объявить тестируемый массив
      char[] A = { 'a', 'c', 'f', '=', '+' };

      // 1.2. Создать экземпляр типа CharArray
      CharArray CA = new CharArray(A);

      // 1.3. Использовать метод Print()
      CA.Print();

      // 2. Вывести названия классов-атрибутов, которые присоединены к методу Print().
      // 2.1. Взять информацию о методе Print() класса tCharArray
      MethodInfo mI = CA.GetType().GetMethod("Print");

      // 2.2. Получить данные об атрибутах, которые присоединены к методу Print()
      object[] attrs = Attribute.GetCustomAttributes(mI);

      // 2.3. Вывести названия атрибутов, присоединенных к методу Print()
      Console.WriteLine("Attributes that are attached to the Print() method:");
      foreach (object at in attrs)
      {
        // 2.3.1. Название атрибута
        Console.Write("=> Attribute name: ");
        Console.WriteLine(at.GetType().Name);

        // 2.3.2. Номер версии
        Console.Write("=> Version number: ");
        PropertyInfo pi = at.GetType().GetProperty("Version");
        Console.WriteLine((string)pi.GetValue(at));
      }
      Console.ReadKey();
    }
  }
}

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

Array CA:
a c f = +
Attributes that are attached to the Print() method:
=> Attribute name: VersionAttribute
=> Version number: 1.0

Объясним некоторые фрагменты вышеприведенного кода.
Первым создается класс-атрибут с именем VersionAttribute с помощью строки

...

[AttributeUsage(AttributeTargets.Method)]
class VersionAttribute : Attribute
{
  ...
}

...

Здесь AttributeUsage — имя встроенного класса-атрибута, который ограничивает применение класса VersionAttribute только для методов. Это ограничение задается значением AttributeTargets.Method из перечисления AttributeTargets. С помощью перечисления AttributeTargets можно задавать ограничения на создание экземпляров только для классов, структур, методов и тому подобное.

Следующим создается класс CharArray, который содержит различные элементы. К методу Print() класса CharArray присоединяется атрибут VersionAttribute следующим образом

...

[Version(version = "1.0")] // version - именованный параметр
public void Print()
{
  Console.WriteLine("Array CA:");
  for (int i = 0; i < ca.Length; i++)
    Console.Write(ca[i] + " ");
  Console.WriteLine();
}

...

При присоединении заполняется именованный параметр version значением «1.0» с помощью строки

Version(version = "1.0")

Параметр version есть public-членом класса VersionAttribute.

Присоединение класса VersionAttribute к методу Print() может быть в иной способ

[Version()]
public void Print()
{
  ...
}

или

[Version]
public void Print()
{
  ...
}

В этом случае будет вызываться конструктор без параметров, в котором внутреннее поле version получит значение «None»

[AttributeUsage(AttributeTargets.Method)]
class VersionAttribute : Attribute
{
  ...

  public VersionAttribute()
  {
    version = "None"; // значение по умолчанию
  }

  ...
}

 


Связанные темы