Deep C# - Custom Attributes In C#
Written by Mike James   
Friday, 19 March 2021
Article Index
Deep C# - Custom Attributes In C#
Attribute Semantics
Practical Attributes
Using Attributes
An Example
Conclusion

Attribute Semantics

The key to understanding what is going on here is to realise that the attribute class you have defined is instantiated when anything queries MyClass’s custom attributes.

 

Banner

 

To query custom attributes we use the GetCustomAttributes static method of the Attribute class:

Attribute.GetCustomAttributes(t)

This returns an array consisting of an instance of each attribute class corresponding to the attributes that have been applied to the specified type t. Notice that this returns an array containing one instance of every attribute applied to t. 

For example the instruction:

Attribute[] attrs = 
       Attribute.GetCustomAttributes(
                            typeof(MyClass));

returns an array with, in this case, a single element attrs[0] which contains an instance of ASCII. A cast to ASCII is all you need to make use of the instance.

This way of retrieving attributes is the one you most often encounter in examples of attribute use. However, in many situations there is an alternative which is arguably better.

In most cases you don’t really want to retrieve all the attributes applied to an entity because you are writing code which is going to handle just one attribute type – i.e. the custom attribute you are currently implementing.

There is a fairly easy way to retrieve specific attributes from the type to which they are applied using the GetCustomAttribute method of the Type object to return attributes of a specified type.

For example, to retrieve any ASCII attributes applied to MyClass you would use:

object[] attrs=typeof(MyClass).
   GetCustomAttributes(typeof(ASCII), false);

This returns an array of objects that can be cast to the attribute type you are looking for.The final Boolean parameter determines if the instance's inheritance chain is searched for attributes - false restricts us to only directly applied attributes.

If you want to make things easier you can perform the cast on the returned array:

ASCII[] attrs=(ASCII[]) typeof(MyClass).
   GetCustomAttributes(typeof(ASCII), false);

This always works because only attributes of ASCII type will be returned or a null reference which can also be cast to ASCII.

 

csharp

Banner

The Attribute Constructor

So far so good but currently the instance of the attribute’s class isn’t doing us a lot of good as the class has no methods and, more importantly, no properties.

There are two ways of providing it with properties. The first is to provide a constructor with parameters.

For example, if we change the ASCII class definition to:

public class ASCII : Attribute
{
 public string MyData;
 public ASCII(string MyDataIn)
 {
  MyData = MyDataIn;
 }
}

we can pass some data to the class by applying the attribute as:

[ASCII("Hello Attribute World")]
public class MyClass
{
}

Now when the GetCustomAttributes method is called it returns a single instance of the ASCII class with MyData set to “Hello Attribute World”.

In other words, calling the GetCustomAttributes method has resulted in the class constructor being used with the data provided by the ASCII attribute tag.

The tag is a call to the constructor and you can think of:

[ASCII("Hello Attribute World") ]

as being equivalent to:

new ASCII("Hello Attribute World");

being executed when you call GetCustomAttributes on any class that the attribute is applied to so returning an instance complete with what ever initialisation the constructor has performed.

To see that you have indeed got an instance of the ASCII class complete with the string set by the constructor try:

ASCII[] attrs=(ASCII[]) typeof(MyClass).
            GetCustomAttributes(
                     typeof(ASCII), false);
MessageBox.Show(attrs[0].MyData);

After casting the returned array to ASCII you can access all its public methods and properties. While this isn’t the way that attributes are normally used it is perfectly valid.

Initialising Fields

As well as using the constructor to initialise the instance, you can also use named parameters to initialise member variables. For example, if we add a public variable MoreData to the attribute class:

public class ASCII : Attribute
{
 public string MyData;
 public string MoreData;
 public ASCII(string MyDataIn)
 {
  MyData = MyDataIn;
 }
}

it can be initialised using:

[ASCII("Hello Attribute World",
                    MoreData="SomeData")]

Now when GetCustomAttributes is called the instance is constructed as follows:

attrs[0]=new ASCII("Hello Attribute World");
attrs[0].MoreData = "SomeData";

Once again you can test this assertion by using a MessageBox:

ASCII[] attrs=(ASCII[]) typeof(MyClass).
                  GetCustomAttributes(
                        typeof(ASCII), false);
MessageBox.Show(attrs[0].MoreData);

where you will discover that the public string MoreData has indeed been set to “SomeData” by the attribute.

csharp

You should now be able to see the general principle:.

  • Any unnamed parameters used in the attribute are passed to the constructor in the order in which they are given.
  • Any named parameters are used as initialisation statements after the instance has been constructed.

Notice that all of the unnamed parameters have to come before the named parameters. So for example, an attribute applied as:

[MyAttribute(a,b,c,... name1=A,name2=B...)]

results in:

MyAttribute MyInstance=
                 new MyAttribute(a,b,c,...);
MyInstance.name1=A;
MyInstance.name2=B;
...

when the GetCustomAttributes method is used.

Notice also that named parameters are optional, if they are not supplied then the corresponding variable is left uninitialised.

It is also important to know that the type of parameter you can use is limited to the majority of the simple types, i.e. int, long, float, string, System.Type, object, a publicly accessible enum and a one-dimensional array of any allowable type.



Last Updated ( Friday, 19 March 2021 )