Page 1 of 4
The role of custom attributes in C# can be confusing. They are easy to use when supplied, but how do you go about creating custom attributes? And when are they useful?
Attributes - they really don't seem to be necessary as part of a programming language. After all why "decorate" code with attributes when you can achieve the same results by more direct routes?
In nearly all cases an attribute is an excuse for not implementing a property that does the same job.
However attributes provide a useful extension to the standard language syntax and a way of adding features after the event and without having to extensively re-work the classes that that attributes are applied to.
In other words, the classes that you apply attributes to don't necessarily need to know anything about the attributes you apply. In this sense attributes have been likened to "code patching" brought up-to-date.
You can use an attribute to add a behaviour without modifying the core code and you can take this behavior away just as easily.
C# has supported and usefully employed attributes for some time but there is still a great deal of confusion about when custom attributes might or might not be useful.
In .NET an attribute is an item of data associated with a programming construct that is stored in the assembly and which can be accessed by the use of reflection.
Notice that some confusion is caused by the fact that some built-in attributes also additionally affect the way that the compiler seems to work. In the main this is not something that most attributes, and certainly not custom attributes, do.
Essentially a custom attribute is a "code comment" that gets compiled into your code, as metadata, and which other code, including you own, can retrieve and possibly act on.
This is the standard explanation of an attribute but the attribute mechanism is so much more.
- When you retrieve an attribute an instance of a class that represents the attribute is created.
Understanding how this mechanism works means that you can think about exactly how attributes might be useful and how things can go wrong.
There is a sense in which an attribute can always be replaced by a property. For example, you might mark a class as using ASCII encoding for its text and hence implement an [ASCII] attribute. Any other class that make use of a class so marked would have to inspect it to discover that it was ASCII and then arrange to treat it correctly.
It should be fairly obvious that an alternative is to use an ASCII property set to True or False to indicate the same information. Notice that the two approaches have some similarities and some differences:
- Any client that uses the class has to implement retrieving and testing the attribute or the property and act accordingly – that is, nothing is enforced in either approach.
- A property can be set at compile time and can be changed at run time. An attribute on the other hand is set at compile time and generally isn’t changed at runtime.
- A property is associated with instances of a class but an attribute can be associated with many different entities including classes, methods, parameters and so on.
- Attributes don’t change and can be associated with a range of entities
This seems to sum up the situation but notice that with such a flexible implementation you can always change the way things work. This is the standard and most appropriate way of working with attributes.
Implementing A Custom Attribute
Implementing a custom attribute such as [ASCII], mentioned in the previous section, is fairly easy as long as you don’t read into it more than there is.
All you have to do is create a class that inherits from Attribute:
public class ASCIIattribute : Attribute
It is usual to use a name of the form Xattribute and if you do the system will treat the names X and Xattribute as the same.
Unfortunately if you do declare the class as Xattribute, e.g. ASCIIattribute, then IntelliSense prompts with the full name. Personally I’d rather keep attribute names short and meaningful so I prefer:
public class ASCII : Attribute
Now you can apply the attribute to any class as in:
public class MyClass
Of course nothing happens if you do this, there is no default attribute behaviour worth worrying about, but it is legal syntax – now to give it some 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.
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:
Attribute attrs =
returns an array with, in this case, a single element attrs 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:
ASCII attrs=(ASCII) typeof(MyClass).
This always works because only attributes of ASCII type will be returned or a null reference which can also be cast to ASCII.