Deep C# - Custom Attributes In C# |
Written by Mike James | |||||||
Friday, 19 March 2021 | |||||||
Page 2 of 6
Attribute SemanticsThe 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.
To query custom attributes we use the GetCustomAttributes static method of the Attribute class:
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:
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:
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:
This always works because only attributes of ASCII type will be returned or a null reference which can also be cast to ASCII.
The Attribute ConstructorSo 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:
we can pass some data to the class by applying the attribute as:
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:
as being equivalent to:
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:
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 FieldsAs 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:
it can be initialised using:
Now when GetCustomAttributes is called the instance is constructed as follows:
Once again you can test this assertion by using a MessageBox:
where you will discover that the public string MoreData has indeed been set to “SomeData” by the attribute.
You should now be able to see the general principle:.
Notice that all of the unnamed parameters have to come before the named parameters. So for example, an attribute applied as:
results in:
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 ) |