Creating Custom Attributes
First and foremost, attributes are classes that are subclassed from the System.Attribute class. The implication is that all the skills you have acquired can be used to generalize the Attribute class and define custom attributes.
Custom attributes allow third parties to participate in extending the .NET framework. To demonstrate creating custom attributes, I will walk you through creating the HelpAttribute introduced in the VS .NET documentation. Let's review a couple of guidelines for creating custom attributes first.
These guidelines are provided to facilitate custom attribute implementation and keep you out of trouble. Like any guidelines, they exist for your benefit, but you can experiment with slight variations and deviate from the guidelines if you have a good reason.
Implementing the HelpAttribute
The HelpAttribute sample class defined in this section was borrowed from the VS .NET help documentation. The original reference is located at ms-help://MS. VSCC/MS.MSDNVS/cpguide/html/cpconwritingcustomattributes. htm#cpconcustomattributeexample.
You can reference that document to find additional information on creating custom attributes. The specific requirements for creating custom attributes are covered here.
Declaring the Attribute Class
Attributes are used by other applications and effectively extend VS .NET resources. You will need to create a Class Library project to end up with a .DLL assembly. By default a Class Library template adds a .vb module with a class defined in it. This is what we need.
After you create the new Class Library, rename the project MyHelpAttribute and rename the class HelpAttribute. The result is a .vb module with the following lines of code:
Public Class HelpAttribute End Class
The rest is almost as easy if you know what each step is and the role of each part of the process.
The next step is to indicate what entities the attribute will be used to describe. We want to allow developers to associate help with any code entity; hence we will indicate this information in AttributeUsageAttribute.
AttributeUsageAttribute defines one positional argument and supports two named arguments. The positional argument indicates the AttributeTargets, and the two named arguments are AllowMultiple and Inherited.
Using the AttributeTargets Argument
AttributeTargets describes the entities that the attribute can be applied to. AttributeTargets is an enumeration. (You can look it up in the help documentation for specifics, but the enumerations are just the names of kinds of code entities: Class, Property, Assembly, Enum, and so forth.)
To indicate that our attribute is suitable for all targets we can add AttributeUsageAttribute to our code, which evolves as follows :
<AttributeUsage(AttributeTargets.All)> Public Class HelpAttribute End Class
Remember that by convention the Attribute suffix is dropped when applied.
Using the AllowMultiple Argument
AllowMultiple is a named argument. From the earlier guidelines, we know that named arguments are used for optional arguments. AllowMultiple is False by default; it indicates whether a given attribute can be applied more than once to the same entity. For our purposes, each entity only needs one help reference.
Attributes that are defined with AllowMultiple:=True are referred to as multiuse attributes. When AllowMultiple:=False, you have a single-use attribute.
Using the Inherited Argument
Inherited is a named argument. Recall that named arguments are passed using the Name := Value syntax between the parentheses and after the positional arguments.
The Inherited named argument is False by default. Inherited indicates whether or not the attribute can be subclassed.
Inheriting from System.Attribute
After we have specified attribute usage, we need to indicate that we are generalizing, or subclassing, the attribute class. The attribute class we are developing evolves as shown.
<AttributeUsage(AttributeTargets.All)> Public Class HelpAttribute Inherits System.Attribute End Class
Implementing the Constructor
We determined that the URN is a positional argument. That is, the URN is a required argument. Consequently this choice guides the implementation of our constructor. We also need a field to store the value in and a read-only property to provide public access to the underlying field.
Applying these choices, we can update the class:
<AttributeUsage(AttributeTargets.All)> Public Class HelpAttribute Inherits System.Attribute Private FUrn As String Public ReadOnly Property Urn() As String Get Return FUrn End Get End Property Public Sub New(ByVal AUrn As String) FUrn = AUrn End Sub End Class
The revision adds the private field, using our F-prefix convention. The read-only property provides public access to the private field, also a convention we employ in object-oriented programming. Finally, the constructor specifies the required positional argument used to initialize the URN, or location, of the help content.
Adding Named Arguments
Finally, we can add named arguments to our attribute class. Named arguments are public fields or private fields represented by public readable and writable properties. (Notice that named arguments are writable.)
Generally, we use private fields and public properties. However, in this case we will deviate and simply add a public field to represent our named argument, Topic. As a final touch, I elected to make the positional argument optional and have HelpAttribute default to my Web site. (In your application you could have this attribute default to your root help document.) Listing 12.11 shows the completed attribute class.
Listing 12.11 A completed HelpAttribute attribute class
1: Imports System 2: 3: <AttributeUsage(AttributeTargets.All)> _ 4: Public Class HelpAttribute 5: Inherits Attribute 6: 7: Private FUrn As String 8: Public Topic As String 9: 10: Public ReadOnly Property Urn() As String 11: Get 12: Return FUrn 13: End Get 14: End Property 15: 16: Public Sub New(Optional ByVal AUrn As String _ 17: = "http://www.softconcepts.com") 18: FUrn = AUrn 19: End Sub 20: 21: End Class
The completed class is quite short, but the attribute allows us to tag any elements of code we choose with any documentation we want.