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.

 


Зв’язані теми