C# .NET. Embedded .NET attributes

Embedded .NET attributes. Attributes [AttributeUsage], [Obsolete], [Conditional], [Serializable], [NonSerialized]

Before studying this topic, it is recommended that you familiarize yourself with the topic:


Contents


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