Embedded .NET attributes. Attributes [AttributeUsage], [Obsolete], [Conditional], [Serializable], [NonSerialized]
Before studying this topic, it is recommended that you familiarize yourself with the topic:
Contents
- 1. Embedded .NET attributes. List
- 2. Attribute [AttributeUsage]. Enumeration AttributeTargets
- 3. An example of using the AttributeUsage attribute
- 4. Attribure [Obsolete]. Example
- 5. Applying the [Conditional] attribute. Example
- 6. Using the [Serializable] and [NonSerialized] attributes. Serialization. Example
- Related topics
Search other resources:
1. Embedded .NET attributes. List
The C# language provides for the use of built-in (standard) attributes (attribute classes), which can be attached to self-developed program elements (classes, methods, structures, etc.). Below are the most common ones:
- AttributeUsage;
- Obsolete;
- Conditional;
- Serialize;
- NonSerialized;
- другие атрибуты.
⇑
2. Attribute [AttributeUsage]. Enumeration AttributeTargets
The AttributeUsage attribute allows you to define the types of elements to which a declared custom attribute can be applied. This means that if a custom attribute class is declared, then it is possible to specify the use of this class strictly for certain program elements, for example, methods, fields, properties, and the like. The list of items is defined in the AttributeTargets enumeration.
The AttributeUsage attribute can only be used for attribute classes, that is, for classes inherited from the System.Attribute class. If you try to use the AttributeUsage attribute on any class that is not an attribute (not inherited from System.Attribute), a compile-time error will be thrown.
In the most general case, the use of the AttributeUsage attribute is as follows:
[AttributeUsage(AttributeTargets validElements)] class MyClassAttribute : Attribute { ... }
here validElements is a list of values from the AttributeTargets enumeration that define elements that can attach the custom attribute class MyClassAttribute. The list of values is formed through the operator | (OR).
The AttributeTargets enumeration defines the following values:
- All or AttributeTargets.All – means that a custom attribute class can be attached to any program element;
- Assembly – a declared (user-defined) attribute can be applied to an assembly;
- Class – the attribute is applied to the class;
- Constructor – the declared attribute can be applied to the constructor;
- Delegate – the attribute can be applied to the delegate;
- Enum – the attribute can be applied to the enumeration;
- Event – the attribute can be applied to the event;
- Field – the attribute can be applied to a field;
- GenericParameter – the attribute can be applied to a generic parameter;
- Interface — the attribute can be applied to the interface;
- Method – the attribute can be applied to a method;
- Module – is applied to the module;
- Parameter – is applied to the parameter;
- Property – is applied to the property;
- ReturnValue – is applied to the return value;
- Struct – is applied to the structure.
⇑
3. An example of using the AttributeUsage attribute
Suppose you need to declare a custom attribute (attribute class) that specifies a comment for an element. You must ensure that this attribute is used strictly for classes, structures, and methods. In this case, declaring an attribute class and attaching it to another class can be, for example, the following
... // Attribute declaration (class attribute), which defines a comment. // A class can be applied strictly to classes, structures and methods. [AttributeUsage( AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method )] class CommentAttribute : Attribute { // internal field-comment positional parameter in the constructor private string comment; // Constructor public CommentAttribute(string _comment) { comment = _comment; } // Property that returns a comment public string Comment { get { return comment; } } } // Attaching the CommentAttribute to the MyClass [Comment("This is a class")] class MyClass { // ... // Attaching the CommentAttribute to a method [Comment("This is a method")] public void Print() { Console.WriteLine("Class MyClass"); } } // Attaching the CommentAttribute to a struct [Comment("This is a structure")] struct XYZ { public double x; public double y; public double z; } ...
If you try to attach an attribute to an element other than a class, structure, or method, the compiler will throw an error. That is, if you try to attach the CommentAttribute to the enumeration
... [Comment("This is a enumeration")] enum Colors { Red = 1, Blue = 2, Yellow = 4 } ...
then the program will not be compiled.
⇑
4. Attribure [Obsolete]. Example
The Obsolete attribute can be used to mark a program element as obsolete. The general form of using (attaching) an attribute is as follows:
[Obsolete(message)]
here message is some message issued by the compiler during compilation of a program element to which the Obsolete attribute is attached (applied).
Example. The example declares a Complex class that implements a complex number. The class declares two overloaded Print() methods that have the following signature:
void Print(); void Print(string msg);
The first Print() method with no parameters is marked (marked) as deprecated using the Obsolete attribute
... [Obsolete("Old version of Print() method.")] public void Print() { Console.WriteLine("re = {0:f2}, im = {1:f2}", re, im); } ...
In the main() function in the line
cm.Print();
the compiler issues a warning that an outdated version of the Print() method is being used.
Below is all the code for a demo program of type Console Application.
using System; namespace ConsoleApp19 { // Class describing a complex number class Complex { // Internal fields - real and imaginary parts of a number private double re, im; // Constructor public Complex(double _re, double _im) { re = _re; im = _im; } // Access properties public double Re { get { return re; } set { re = value; } } public double Im { get { return im; } set { im = value; } } // A method that outputs a complex number is a deprecated option [Obsolete("Old version of Print() method.")] public void Print() { Console.WriteLine("re = {0:f2}, im = {1:f2}", re, im); } // Method that outputs a complex number - new version 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. Calling the old version of the Print() method, // the compiler issues a warning here // that the deprecated version of the Print() method is used Console.Write("cm: "); cm.Print(); // 2. Call new version of Print() method cm.Print("cm"); Console.ReadKey(); } } }
The result of the program
cm: re = 2.50, im = 1.80 cm: re = 2.50, im = 1.80
⇑
5. Applying the [Conditional] attribute. Example
The [Conditional] attribute provides method invocation based on the identifier defined in the #define directive. In other words, if a certain identifier is defined in the program code using the #define directive
#define Identifier
then in the instance of the class the method marked with this identifier will be called
class SomeClass { ... // Applying the [Conditional] attribute to a method [Conditional("Identifier") return_type MethodName(parameters) { // This method will be called // ... } }
here
- return_type – the type that the method named MethodName() returns;
- parameters – parameters of the MethodName() method.
All other methods marked with a different identifier will fail. However, those methods to which the [Conditional] attribute is attached will be executed.
To apply the [Conditional] attribute, the program must include the System.Diagnostics namespace
using System.Diagnostics;
Example. The example demonstrates the application of the [Conditional] attribute with different identifiers to different methods of the TestMethods class.
// Define the identifier DoMethod2 #define DoMethod2 using System; using System.Diagnostics; namespace ConsoleApp19 { // A class containing methods to which the [Conditional] attribute is attached class TestMethods { // The [Conditional] attribute is attached to the Method1() method [Conditional("DoMethod1")] public void Method1() { Console.WriteLine("Method1()"); } // The [Conditional] attribute is attached to the Method2() method [Conditional("DoMethod2")] public void Method2() { Console.WriteLine("Method2()"); } // The [Conditional] attribute is attached to the Method2() method [Conditional("DoMethod3")] public void Method3() { Console.WriteLine("Method3()"); } // There are no attributes attached to Method4() public void Method4() { Console.WriteLine("Method4()"); } } class Program { static void Main(string[] args) { // Demonstration of operation of the [Conditional] attribute // Create the instance of TestMethods class TestMethods tm = new TestMethods(); // Call 4 methods in sequence tm.Method1(); // method will not be executed tm.Method2(); // method will be executed tm.Method3(); // method will not be executed tm.Method4(); // method will be executed Console.ReadKey(); } } }
The result of the program
Method2() Method4()
As you can see from the result, the following two of the four methods are called:
- Method2() – the [Conditional] attribute with the “Method2” identifier, which is defined in the #define directive at the beginning of the program, is attached to this method;
- Method4() – a method to which no attributes have been applied.
⇑
6. Using the [Serializable] and [NonSerialized] attributes. Serialization. Example
If you need a class or structure to support serialization, you must specify the [Serializable] attribute before declaring it. Serialization support for a class or structure means that the class (structure) has the ability to persist its state in the byte stream. When the state is saved, the data of the class is actually stored, which are the internal fields of the class. The general form of declaring a class that supports serialization is as follows:
[Serializable] class SomeClass { ... }
There are times when from a set of internal fields of a class, it is not necessary to store them all. The [NonSerialized] attribute is used to indicate class fields that do not need to be serialized. The [NonSerialized] attribute applies only to class fields. In the simplest case, applying the [NonSerialized] attribute to a field looks like
[Serializable] class SomeClass { ... [NonSerialized] type fieldName; ... }
here
- fieldName – the name of the field in the SomeClass class;
- type – field type fieldName.
Example.
A Book structure is specified that describes a book in the library. The structure declares the following public fields:
- title – title of the book;
- author – the name of the author of the book;
- year – year of publication of the book;
- number – ordinal number of the book in the list of books.
The Book structure supports binary serialization. The [Serializable] attribute is attached to the structure to support serialization. Storing the structure in the byte stream is done using the BinaryFormatter class. The BinaryFormatter class implements the IFormatter interface. This interface defines two methods that are applied to a struct instance:
- Serialize() – saves the structure in the byte stream;
- Deserialize() – restores the Book structure from the byte stream.
The program demonstrates serialization of an instance of a structure of type Book. The number field is not stored in the byte stream (not serializable).
The demo code is as follows.
using System; using System.IO; // needed for I/O operations // Required to use the BinaryFormatter class using System.Runtime.Serialization.Formatters.Binary; namespace ConsoleApp19 { // The structure describing the book. // The structure supports serialization. [Serializable] struct Book { // These fields will be serialized (saved) public string title; public string author; public int year; [NonSerialized] public int number; // this field is not serialized // Constructor public Book(string title, string author, int year, int number) { this.title = title; this.author = author; this.year = year; this.number = number; } // Method that displays information about the book 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. Create the instance of Book type Book b1 = new Book("The C Programming Language", "D. Ritchie", 1978, 1); // 2. Create the instance of BinaryFormatter type BinaryFormatter binaryFormat = new BinaryFormatter(); try { // 3. Save the b1 structure instance data to the books.bin file using (Stream fOut = File.Create("books.bin")) { binaryFormat.Serialize(fOut, b1); } Console.WriteLine("Binary serialize is done."); // 4. Read data from books.bin file into another b2 structure // and display the read information on the screen. 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(); } } }
The result of the program
Binary serialize is done. The instance of b2: Author: D. Ritchie Title: The C Programming Language Year: 1978 Binary deserialize is done.
⇑
Related topics
- Attributes. The role of attributes. The need to use attributes. Custom attributes
- Constructors in attribute classes. Positional and named parameters. Examples
⇑