C#. Встроенные атрибуты .NET

Встроенные атрибуты .NET. Атрибуты [AttributeUsage], [Obsolete], [Conditional], [Serializable], [NonSerialized]

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


Содержание


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




1. Встроенные атрибуты .NET. Обзор. Перечень

Язык C# предусматривает использование встроенных (стандартных) атрибутов (классов-атрибутов), которые можно присоединять к собственно-разработанных элементов программы (классов, методов, структур и т.д.). Ниже приведены наиболее распространенные из них:

  • AttributeUsage;
  • Obsolete;
  • Conditional;
  • Serialize;
  • NonSerialized;
  • другие атрибуты.

 

2. Атрибут [AttributeUsage]. Перечисление AttributeTargets

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

Атрибут AttributeUsage может быть использован только для классов-атрибутов, то есть для классов, унаследованных от класса System.Attribute. Если попытаться использовать атрибут AttributeUsage для любого класса, который не является атрибутом (не унаследован от System.Attribute), то возникнет ошибка на этапе компиляции.

В наиболее общем случае использование атрибута AttributeUsage имеет следующий вид:

[AttributeUsage(AttributeTargets validElements)]
class MyClassAttribute : Attribute
{
  ...
}

здесь validElements — перечень значений из перечисления AttributeTargets, определяющих элементы, которые могут присоединять пользовательский класс-атрибут MyClassAttribute. Перечень значений формируется через оператор | (ИЛИ).

Перечисление AttributeTargets определяет следующие значения:

  • All или AttributeTargets.All — означает, что пользовательский класс-атрибут может быть присоединен к любому элементу программы;
  • Assembly — объявляемый (пользовательский) атрибут может быть применен к сборке;
  • Class — атрибут применяется к классу;
  • Constructor — объявляемый атрибут может быть применен к конструктору;
  • Delegate — атрибут может быть применен к делегату;
  • Enum — атрибут может быть применен к перечислению;
  • Event — атрибут может быть применен к событию;
  • Field — атрибут может быть применен к полю;
  • GenericParameter — атрибут может быть применен к обобщенному параметру;
  • Interface — атрибут может быть применен к интерфейсу;
  • Method — атрибут может быть применен к методу;
  • Module — применяется к модулю;
  • Parameter — применяется к параметру;
  • Property — применяется к свойству;
  • ReturnValue — применяется к возвращаемому значению;
  • Struct — применяется к структуре.

 

3. Пример применения атрибута AttributeUsage

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

...

// Объявление атрибута (класса-атрибута), который определяет комментарий.
// Класс может применяться строго к классам, структурам и методам.
[AttributeUsage(
  AttributeTargets.Class |
  AttributeTargets.Struct |
  AttributeTargets.Method
)]
class CommentAttribute : Attribute
{
  // внутреннее поле-комментарий, позиционный параметр в конструкторе
  private string comment;

  // Конструктор класса
  public CommentAttribute(string _comment)
  {
    comment = _comment;
  }

  // Свойство, возвращающее комментарий
  public string Comment
  {
    get { return comment; }
  }
}

// Присоединение атрибута CommentAttribute к классу MyClass
[Comment("This is a class")]
class MyClass
{
  // ...

  // Присоединение атрибута CommentAttribute к методу
  [Comment("This is a method")]
  public void Print()
  {
    Console.WriteLine("Class MyClass");
  }
}

// Присоединение атрибута CommentAttribute к структуре
[Comment("This is a structure")]
struct XYZ
{
  public double x;
  public double y;
  public double z;
}

...

Если попытаться присоединить атрибут к другому элементу кроме класса, структуры, метода то компилятор выдаст ошибку. То есть, если попытаться присоединить атрибут CommentAttribute к перечислению

...

[Comment("This is a enumeration")]
enum Colors
{
  Red = 1,
  Blue = 2,
  Yellow = 4
}

...

то программа не будет откомпилирована.

 

4. Атрибут [Obsolete]. Пример

С помощью атрибута Obsolete можно отметить элемент программы как устаревший. Общая форма использования (присоединения) атрибута следующая:

[Obsolete(message)]

здесь message — некоторое сообщение, выдаваемое компилятором во время компиляции элемента программы, к которому присоединяется (применяется) атрибут Obsolete.

Пример. В примере объявляется класс Complex, который реализует комплексное число. В классе объявляются два перегруженых метода Print(), которые имеют следующую сигнатуру:

void Print();
void Print(string msg);

Первый метод Print() без параметров отмечен (маркирован) как устарелый с помощью атрибута Obsolete

...
[Obsolete("Old version of Print() method.")]
public void Print()
{
  Console.WriteLine("re = {0:f2}, im = {1:f2}", re, im);
}
...

В функции main() в строке

cm.Print();

компилятор выдает предупреждение, что применяется устаревшая версия метода Print().

Ниже приведен весь код демонстрационной программы типа Console Application.

using System;

namespace ConsoleApp19
{
  // Класс, описывающий комплексное число
  class Complex
  {
    // Внутренние поля - вещественная и мнимая части числа
    private double re, im;

    // Конструктор
    public Complex(double _re, double _im)
    {
      re = _re;
      im = _im;
    }

    // Свойства доступа
    public double Re
    {
      get { return re; }
      set { re = value; }
    }

    public double Im
    {
      get { return im; }
      set { im = value; }
    }

    // Метод, который выводит комплексное число - устаревший вариант
    [Obsolete("Old version of Print() method.")]
    public void Print()
    {
      Console.WriteLine("re = {0:f2}, im = {1:f2}", re, im);
    }

    // Метод, который выводит комплексное число - новая версия
    public void Print(string msg)
    {
      Console.Write(msg + ": ");
      Console.WriteLine("re = {0:f2}, im = {1:f2}", re, im);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      Complex cm = new Complex(2.5, 1.8);

      // 1. Вызов старой версии метода Print(),
      //    здесь компилятор выдает предупреждение (warning)
      //    что используется устаревшая версия метода Print()
      Console.Write("cm: ");
      cm.Print();

      // 2. Вызов новой версии метода Print()
      cm.Print("cm");
      Console.ReadKey();
    }
  }
}

Результат выполнения программы

cm: re = 2.50, im = 1.80
cm: re = 2.50, im = 1.80

 

5. Применение атрибута [Conditional]. Пример

Атрибут [Conditional] обеспечивает вызов методов на основе идентификатора, который определен в директиве #define. Иными словами, если в коде программы определен некоторый идентификатор с помощью директивы #define

#define Identifier

то в экземпляре класса будет вызываться метод, который помечен этим идентификатором

class SomeClass
{
  ...

  // Присоединение атрибута [Conditional] к методу
  [Conditional("Identifier")
  return_type MethodName(parameters)
  {
    // Этот метод будет вызываться
    // ...
  }
}

здесь

  • return_type – тип, который возвращает метод с именем MethodName();
  • parameters – параметры метода MethodName().

Все другие методы, помеченные другим идентификатором, выполняться не будут. Однако будут выполняться те методы, к которым атрибут [Conditional] присоединен.

Чтобы применить атрибут [Conditional], в программе нужно подключить пространство имен System.Diagnostics

using System.Diagnostics;

Пример. В примере демонстрируется применение атрибута [Conditional] с различными идентификаторами к различным методам класса TestMethods.

// Определить идентификатор DoMethod2
#define DoMethod2

using System;
using System.Diagnostics;

namespace ConsoleApp19
{
  // Класс, содержащий методы, к которым прикреплен атрибут [Conditional]
  class TestMethods
  {
    // К методу Method1() присоединен атрибут [Conditional]
    [Conditional("DoMethod1")]
    public void Method1()
    {
      Console.WriteLine("Method1()");
    }

    // К методу Method2() присоединен атрибут [Conditional]
    [Conditional("DoMethod2")]
    public void Method2()
    {
      Console.WriteLine("Method2()");
    }

    // К методу Method3() присоединен атрибут [Conditional]
    [Conditional("DoMethod3")]
    public void Method3()
    {
      Console.WriteLine("Method3()");
    }

    // К методу Method4() не присоединено никаких атрибутов
    public void Method4()
    {
      Console.WriteLine("Method4()");
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Демонстрация работы атрибута [Conditional]
      // Создать экземпляр класса TestMethods
      TestMethods tm = new TestMethods();

      // Вызвать последовательно 4 метода
      tm.Method1(); // метод не будет выполнен
      tm.Method2(); // метод выполнится
      tm.Method3(); // метод не будет выполнен
      tm.Method4(); // метод выполнится

      Console.ReadKey();
    }
  }
}

Результат выполнения программы

Method2()
Method4()

Как видно из результата, вызываются следующие два метода из четырех:

  • Method2() — к этому методу присоединен атрибут [Conditional] с идентификатором «Method2», который определен в директиве #define в начале программы;
  • Method4() — метод, к которому никаких атрибутов не применено.

 

6. Использование атрибутов [Serializable] и [NonSerialized]. Сериализация. Пример

Если нужно, чтобы класс или структура поддерживали сериализацию нужно перед их объявлением указать атрибут [Serializable]. Поддержка сериализации для класса или структуры означает, что этот класс (структура) имеет возможность сохранять свое состояние в байтовом потоке. При сохранении состояния, фактически хранятся данные класса, каковыми есть внутренние поля класса.

Общая форма объявления класса, поддерживающего сериализацию, следующая:

[Serializable]
class SomeClass
{
  ...
}

Бывают случаи, когда из набора внутренних полей класса, не обязательно хранить их всех. Для того, чтобы указать поля класса, которые не нужно сериализовать, используется атрибут [NonSerialized]. Атрибут [NonSerialized] применяется только к полям класса. В наиболее простом случае применение атрибута [NonSerialized] к полю имеет вид

[Serializable]
class SomeClass
{
  ...

  [NonSerialized]
  type fieldName;

  ...
}

здесь

  • fieldName – имя поля в классе SomeClass;
  • type – тип поля fieldName.

Пример.

Дана структура Book, описывающая книгу в библиотеке. В структуре объявляются следующие общедоступные поля:

  • поле title — название книги;
  • поле author — имя автора книги;
  • year — год издания книги;
  • number — порядковый номер книги в списке книг.

Структура Book поддерживает сериализацию в бинарном формате. Для поддержки сериализации к структуре присоединяется атрибут [Serializable]. Сохранение структуры в байтовом потоке осуществляется с помощью класса BinaryFormatter. Класс BinaryFormatter реализует интерфейс IFormatter. В этом интерфейсе определены два метода, которые применяются к экземпляру структуры:

  • Serialize() — осуществляет сохранение структуры в байтовом потоке;
  • Deserialize() — осуществляет восстановление структуры Book из байтового потока.

В программе происходит демонстрация сериализации экземпляра структуры типа Book. Поле number не сохраняется в байтовом потоке (не сериализуеться).

Код демонстрационной программы следующий.

using System;
using System.IO; // нужно для операций ввода/вывода

// Необходимо для использования класса BinaryFormatter
using System.Runtime.Serialization.Formatters.Binary;

namespace ConsoleApp19
{
  // Структура, описывающая книгу.
  // Структура поддерживает сериализацию.
  [Serializable]
  struct Book
  {
    // Эти поля будут сериализированы (сохранены)
    public string title;
    public string author;
    public int year;

    [NonSerialized]
    public int number; // это поле не сериализуется

    // Конструктор
    public Book(string title, string author, int year, int number)
    {
      this.title = title;
      this.author = author;
      this.year = year;
      this.number = number;
    }

    // Метод, выводящий информацию о книге
    public void Print(string msg)
    {
      Console.WriteLine(msg);
      Console.WriteLine("Author: " + author);
      Console.WriteLine("Title: " + title);
      Console.WriteLine("Year: " + year);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // 1. Создать экземпляр типа Book
      Book b1 = new Book("The C Programming Language", "D. Ritchie", 1978, 1);

      // 2. Создать экземпляр типа BinaryFormatter
      BinaryFormatter binaryFormat = new BinaryFormatter();

      try
      {
        // 3. Сохранить в файле books.bin данные экземпляра структуры b1
        using (Stream fOut = File.Create("books.bin"))
        {
          binaryFormat.Serialize(fOut, b1);
        }
        Console.WriteLine("Binary serialize is done.");

        // 4. Прочитать данные из файла books.bin в другую структуру b2
        //    и вывести прочитанную информацию на экран.
        using (Stream fIn = File.OpenRead("books.bin"))
        {
          Book b2;
          b2 = (Book)binaryFormat.Deserialize(fIn);
          b2.Print("The instance of b2:");
          Console.WriteLine("Binary deserialize is done.");
        }
      }
      catch (Exception e)
      {
        Console.WriteLine(e.Message);
      }

      Console.ReadKey();
    }
  }
}

Результат выполнения программы

Binary serialize is done.
The instance of b2:
Author: D. Ritchie
Title: The C Programming Language
Year: 1978
Binary deserialize is done.

 


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