C# .NET. Constructors in attribute classes

Constructors in attribute classes. Positional and named parameters. Examples

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

 

1. Constructors in attribute classes. Features of use. Example

Typically, attribute classes contain a small number of internal fields (data). These internal fields are initialized using constructors. When attaching attribute classes to other classes, you must adhere to the correct specification of the parameters of these constructors.

For example, if the attribute class has a constructor with parameters of type string and int

// Some attribute class
class MyClassCommentAttribute : Attribute
{
  // Internal fields
  string field1;
  int field2;

  // Constructor with parameters of type string, int
  public MyClassCommentAttribute(string _param1, int _param2)
  {
    // ...
  }
}

then when attaching this class to another class, you need to adhere to the correct order and signature in the parameters

...
[MyClassComment("Some comment", 5)]
class MyClass
{
  // ...
}
...

In the above code, in the line

[MyClassComment("Some comment", 5)]

the constructor of the MyClassCommentAttribute class is called. The constructor receives the first parameter the string “Some comment”, the second parameter an integer value 5. If you try to change the number or types of values passed to the constructor, a compile-time error will occur.

 

2. Positional and named parameters. Definition. Example

There are two types of parameters in the constructors of attribute classes:

  • positional parameters. In these parameters, the argument is bound to the parameter according to its position in the argument list. If multiple positional arguments are passed to the constructor of an attribute class, then the first argument is associated with the first parameter, the second argument is associated with the second parameter, and so on. Typically, positional parameters initialize the internal fields of the attribute class;
  • named parameters. This is the case when a parameter is initialized by its name. In this case, the name of the parameter is important, not its position. Named parameters can only be public fields or properties of an attribute class.

In the most general case, the declaration of an attribute class containing a constructor with positional parameters can be as follows:

class NameOfClassAttribute : Attribute
{
  ...

  // Constructor in which parameters are passed by their positions
  public NameOfClassAttribute(
    type1 pos_parameter1,
    type2 pos_parameter2,
    ...
    typeN pos_parameterN)
  {
    ...
  }

  ...
}

After a constructor containing a list of positional parameters is defined in an attribute class, this class can be attached to any other class in the following general form:

[NameOfClassAttribute(
  pos_parameter1,
  pos_parameter2,
  ...
  pos_parameterN,
  named_parameter_1 = value1,
  named_parameter_2 = value2,
  ...
  named_parameter_N = valueN)]
class SomeClass
{
  ...
}

here

  • NameOfClassAttribute An attribute class that is attached to another class named SomeClass;
  • pos_parameter1, pos_parameter2, pos_parameterN – positional parameter names;
  • name_parameter1, named_parameter2, named_parameterN – the names of named parameters, which are assigned the values value1, value2, valueN respectively.

 

3. An example of using a named parameter

Let the class Car be given, which describes information about the car. Attached to the class is the CommentAttribute, which adds some explanation (comment) to the class.

The Car class implementation contains the following elements:

  • internal fields model and year, which define the car brand and year of manufacture;
  • constructor with two parameters;
  • properties Model and Year, which return the values of internal fields.

The CommentAttribute class inherits from the AttributeUsage class, which restricts the attachment of CommentAttribute to classes only. The following elements are implemented in the CommentAttribute class:

  • a public comment field that contains a comment. When attached to the Car class, this field will be specified as a named parameter;
  • constructor without parameters. In the body of the constructor, the comment field is initialized to its default value.

The CommentAttribute class can be attached to any other class.

In the main() function, using reflection, information about the attributes attached to the Car class is displayed.

The demo program is as follows:

using System;
using System.Reflection;

namespace ConsoleApp18
{
  // 1. Attribute class defining a public comment
  [AttributeUsage(AttributeTargets.Class)] // use only for classes
  class CommentAttribute : Attribute
  {
    public string comment;

    // Parameterless constructor
    public CommentAttribute()
    {
      comment = "No comments."; // default value
    }
  }

  // 2. Class describing vehicle data.
  //   Attached to the class is the CarAttribute with the comment value "This is a car".
  [Comment(comment = "This is a car!")] // here comment - named parameter
  class Car
  {
    // Internal fields
    private string model;
    private int year;

    // Constructor
    public Car(string _model, int _year)
    {
      model = _model;
      year = _year;
    }

    // Fields access properties
    public string Model
    {
      get { return model; }
    }

    public int Year
    {
      get { return year; }
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Demonstration of use
      // 1. Create an instance of the Car class
      Car car = new Car("Renault", 2008);

      // 2. Display information about the attached CommentAttribute
      object[] attrs = Attribute.GetCustomAttributes(car.GetType());

      foreach (object at in attrs)
      {
        // 2.1. Print the name of the attached attribute based on the at instance
        Console.WriteLine("Attribute name: " + at.GetType().Name);

        // 2.2. Print the value of the comment that is attached to the Car class
        // 2.2.1. Get an instance of Type
        Type tp = at.GetType();

        // 2.2.2. Get information about the comment field
        FieldInfo fi = tp.GetField("comment");

        // 2.2.3. Get the value of comment field
        string comment = (string)fi.GetValue(at);

        // 2.2.4. Display comment on screen
        Console.WriteLine("comment = " + comment); // This is a car!
      }
      Console.ReadKey();
    }
  }
}

After running for execution, the program will give the following result

Attribute name: CommentAttribute
comment = This is a car!

Let’s explain some code snippets. Attribute class is created in strings

...
[AttributeUsage(AttributeTargets.Class)] // use only for classes
class CommentAttribute : Attribute
{
  ...
}
...

As you can see from the above code, the class inherits from the built-in AttributeUsage class. This class allows you to restrict the use of custom attribute classes to specific elements. In our case, the AttributeUsage class constructor specifies the AttributeTargets.Class value. This means that our CommentAttribute class can only be attached to classes. It cannot be attached to other elements (structures, enumerations, methods, etc.).

Attachment of the CommentAttribute class to the Car class is carried out in lines

...
[Comment(comment = "This is a car!")] // здесь comment - именованный параметр
class Car
{
  ...
}
...

As you can see from the lines above, you do not need to specify the fully qualified name of the CommentAttribute class. It is enough to specify only the name Comment. However, if you specify the CommentAttribute, then there will be no error.

When attached, the constructor of the CommentAttribute class is called. In this case, the specific value of the named parameter comment is set.

Comment(comment = "This is a car!")

This means that the CommentAttribute class is attached to the car class.

There is another way to attach an attribute class. With this method, you do not need to specify a named parameter. Attaching can be implemented by default as follows

[Comment] // no named parameter specified
class Car
{
  ...
}

or

[Comment()]
class Car
{
  ...
}

In this case, the internal comment field will be initialized with the default value “No comments.”, Which is set in the constructor of the CommentAttribute class.

...
class CommentAttribute : Attribute
{
  ...

  // Parameterless constructor
  public CommentAttribute()
  {
    comment = "No comments."; // default value
  }
}

 

4. An example of using positional and named parameters in an attribute class. The constructor takes two parameters

The example declares a class named SomeClassAttribute. The following elements are declared in the class:

  • internal hidden (private) fields named Int and Double;
  • public fields named fString and fFloat;
  • a constructor that takes two positional parameters.

For demonstration purposes, the SomeClassAttribute class is attached to four classes named Class1, Class2, Class3, Class4 in different ways:

  • without specifying named parameters (Class1);
  • specifying one named parameter (Class2, Class3);
  • specifying two named parameters (Class4).
...

// Some class that contains a constructor that takes two positional parameters
class SomeClassAttribute : Attribute
{
  // Internal hidden fields of the class
  private int fInt;
  private double fDouble;

  // public fields
  public string fString;
  public float fFloat;

  // Constructor that takes two positional parameters
  public SomeClassAttribute(
    int param1,
    double param2)
  {
    // Initializing hidden fields with positional parameter values
    fInt = param1;
    fDouble = param2;

    // Initializing public fields in the constructor to default values
    fString = "None";
    fFloat = 1.0f;
  }
}

// Attaching the NameOfClassAttribute class to another class.
// Case 1 - no named parameters are specified
[SomeClass(25, 8.83)]
class Class1
{
  // ...
}

// Case 2 - only one named parameter of type string is specified
[SomeClassAttribute(3, 7.55, fString = "SomeClass2")]
class Class2
{
  // ...
}

// Case 3 - only one named parameter of type float is specified
[SomeClass(8, 1.25, fFloat = 9.5f)]
class Class3
{
  // ...
}

// Case 4 - two named parameters are specified
[SomeClass(1, -10.5, fFloat = 1.8f, fString = "SomeClass3")]
class Class4
{
  // ...
}

...

 

5. Applying an attribute to a method. Example

Attributes can be attached to class methods. The following is an example of attaching the VersionAttribute to the Print() method of the CharArray class. The attribute specifies the version number for the Print() method.

The main() function displays data about the attached attribute:

  • the name of the attribute class;
  • the value of the instance field, which is the version number of the method.
using System;
using System.Reflection;

namespace ConsoleApp18
{
  // 1. Declare a class that is an attribute.
  //   The class describes a string that defines the version of the element.
  //    This attribute class is used only for the method.
  [AttributeUsage(AttributeTargets.Method)]
  class VersionAttribute : Attribute
  {
    // 1.1. public field - version of the method
    public string version;

    // 1.2. Parameterless constructor
    public VersionAttribute()
    {
      version = "None"; // default value
    }

    // 1.3. Version access property
    public string Version
    {
      get { return version; }
    }
  }

  // 2. A class describing an array of characters of type char[].
  //   The class has a Print() method to which the VersionAttribute attribute is attached
  class CharArray
  {
    // 2.1. Internal field
    private char[] ca;

    // 2.2. Constructor
    public CharArray(char[] _ca)
    {
      ca = _ca;
    }

    // 2.3. The Print() method to which the VersionAttribute is attached
    [Version(version = "1.0")] // version - named parameter
    public void Print()
    {
      Console.WriteLine("Array CA:");
      for (int i = 0; i < ca.Length; i++)
        Console.Write(ca[i] + " ");
      Console.WriteLine();
    }

    // 2.4. Свойство доступа к полю ca
    public char[] Value
    {
      get { return ca; }
    }
  }

  class Program
  {

    static void Main(string[] args)
    {
      // 1. Using the CharArray class
      // 1.1. Declare the array under test
      char[] A = { 'a', 'c', 'f', '=', '+' };

      // 1.2. Create an instance of the CharArray type
      CharArray CA = new CharArray(A);

      // 1.3. Use the Print() method
      CA.Print();

      // 2. Print the names of the attribute classes that are attached to the Print() method.
      // 2.1. Get information about the Print() method of the tCharArray class
      MethodInfo mI = CA.GetType().GetMethod("Print");

      // 2.2. Get data about the attributes that are attached to the Print() method
      object[] attrs = Attribute.GetCustomAttributes(mI);

      // 2.3. Print the names of the attributes attached to the Print() method
      Console.WriteLine("Attributes that are attached to the Print() method:");
      foreach (object at in attrs)
      {
        // 2.3.1. The name of the attribute
        Console.Write("=> Attribute name: ");
        Console.WriteLine(at.GetType().Name);

        // 2.3.2. Version number
        Console.Write("=> Version number: ");
        PropertyInfo pi = at.GetType().GetProperty("Version");
        Console.WriteLine((string)pi.GetValue(at));
      }
      Console.ReadKey();
    }
  }
}

After running for execution, the program produces the following result

Array CA:
a c f = +
Attributes that are attached to the Print() method:
=> Attribute name: VersionAttribute
=> Version number: 1.0

Let’s explain some of the above code snippets.

First, an attribute class named VersionAttribute is created using the string

...
[AttributeUsage(AttributeTargets.Method)]
class VersionAttribute : Attribute
{
  ...
}
...

Here AttributeUsage is the name of the built-in attribute class that restricts the VersionAttribute class to methods only. This limit is set by the AttributeTargets.Method value from the AttributeTargets enumeration. Using the AttributeTargets enumeration, you can set instantiation restrictions only on classes, structures, methods, and the like.

Next, the CharArray class is created, which contains various elements. Attached to the Print() method of the CharArray class is the VersionAttribute as follows

...

[Version(version = "1.0")] // version - named parameter
public void Print()
{
  Console.WriteLine("Array CA:");
  for (int i = 0; i < ca.Length; i++)
    Console.Write(ca[i] + " ");
  Console.WriteLine();
}

...

When attached, the named parameter version is filled with the value “1.0” using the string

Version(version = "1.0")

The version parameter is a public member of the VersionAttribute class.

Attaching the VersionAttribute class to the Print() method can be done in a different way.

[Version()]
public void Print()
{
  ...
}

or

[Version]
public void Print()
{
  ...
}

In this case, the constructor without parameters will be called, in which the internal version field will be set to “None”

[AttributeUsage(AttributeTargets.Method)]
class VersionAttribute : Attribute
{
  ...

  public VersionAttribute()
  {
    version = "None"; // default value
  }

  ...
}