Встроенные атрибуты .NET. Атрибуты [AttributeUsage], [Obsolete], [Conditional], [Serializable], [NonSerialized]
Перед изучением данной темы рекомендуется ознакомиться с темой:
Содержание
- 1. Встроенные атрибуты .NET. Обзор. Перечень
- 2. Атрибут [AttributeUsage]. Перечисление AttributeTargets
- 3. Пример применения атрибута AttributeUsage
- 4. Атрибут [Obsolete]. Пример
- 5. Применение атрибута [Conditional]. Пример
- 6. Использование атрибутов [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.
⇑
Связанные темы
- Роль атрибутов. Необходимость использования атрибутов. Пользовательские атрибуты
- Конструкторы в классах атрибутов. Позиционные и именованные параметры. Примеры
⇑