|Custom Attributes In C#|
|Written by Mike James|
|Tuesday, 23 June 2015|
Page 3 of 4
Now that we have the basic mechanism that is used to implement attributes out in the open and perfectly clear we need to investigate some of the intricacies of using attributes in a real situation.
The first thing to say is that you can apply attributes to more than just a class. To restrict what an attribute can be applied to you need to apply the AttributeUsage attribute to the class that is associated with your custom attribute - yes an attribute applied to your custom attribute!
restricts the ASCII attribute to being applied to methods only. If you try to apply it to anything else the compiler will generate an error message.
IntelliSense provides a list of AttributeTargets and you can OR targets together to produce a set of things that the attribute can be applied to - an example of this is given later.
Being able to tag other entities with attributes raises the question of how to get at them at runtime?
The answer is that there a number of overloaded versions of GetCustomAttributes which return arrays of attributes associated with different types of entity.
For example, GetCustomAttributes(memberinfo) will retrieve an array of attributes applied to the member specified by the memberinfo class.
To see how this works first add a member function to MyClass and tag it with the ASCII attribute:
To retrieve attributes associated with a member we first have to retrieve its memberinfo. There are many ways of doing this but in most cases specifying it by name is sufficient using the GetMember function:
The GetMember function retrieves all members that match the name specified string which can include wildcard characters.
In this case it should just return a memberinfo class corresponding to the unique MyMethod function. To retrieve any attributes applied to the method we can use the usual:
Or if you just want the ASCII attributes that have been applied we can use the alternative:
The GetMember function will return members of a range of types – methods, properties etc.
You can be more specific if you want to and use functions such as GetMethod to return a single MethodInfo class that is specified by name and other information. For example:
There is also a GetMethods function which will return an array of methods that match a name that includes wildcard characters. You can return fields, properties, interfaces and so on and use the corresponding GetCustomAttributes to return an array of attributes applied to them.
Retrieving attributes is generally straightforward – use reflection to get the entity and then call GetCustomAttributes.
One slightly more complicated situation is when an attribute is applied to a parameter. In this case we have to first retrieve the method, then the parameters and then the attributes. To see this in action change the ASCII attribute to read:
Notice that this is an example of OR-ing together parameters used in the constructor call.
The attribute can now be applied to methods and parameters:
To retrieve the attribute we first need to get the method:
Then we need to get the parameters of the method:
Finally we can get and access the parameter attribute:
As well as controlling what an attribute can be applied to, you can also control how many times it can be applied using the AttributeUsage attribute and whether or not it is inherited.
For example if you change the ASCII class definition to:
you can place multiple attributes on a single entity and the attribute is inherited. For example, following:
GetCustomAttributes will return an array with two instances of ASCII with MyData set to the corresponding string.
Similarly, if you create a new class that inherits from MyClass the attributes are also inherited. For example:
has two ASCII attributes exactly the same as MyClass.
You can, of course forbid multiple attributes and stop attribute inheritance by setting the AllowMultiple and Inherited to false.
It is very difficult to find a convincing practical example that uses attributes that is general enough to convey the principles and ideas.
If you look at the way attributes are used in C# and in the Framework you will soon discover that they have a limited application without extending the way that they behave to affect the compiler. Of course using custom attributes you can’t do this.
Some built-in attributes do give you a good idea of what they are good at without extra tricks.
For example, the whole p/invoke and com interop marshaling of parameters is a perfect example. You apply marshaling attribute tags to the parameters of external methods you want to call and when you use them the Framework/CLR uses these attributes to decide how to transform managed data into the correct unmanaged data and vice versa.
What makes this a good use of attributes is that the marshaling has to be applied at the class level and once applied isn’t going to be changed at the instance level, and the code that processes the instances can keep all of the mechanisms that load and respond to the attributes applied hidden from view.
As far as the user of the attributes is concerned it’s a matter of applying the attribute, then ignoring it in all the code they write but when they use some supplied machinery the attribute makes it work correctly.
|Last Updated ( Tuesday, 23 June 2015 )|