Attributes are simply classes derived from the System.Attribute class. Attributes convey metadata ”extra information ”about entities in .NET. For this reason they are an essential part of .NET. Attributes can be subclassed just like any other class, thus permitting you to create custom attributes.
To create a custom attribute you need to create a new class that inherits from System.Attribute , apply the AttributeUsageAttribute to that class, and define named and positional arguments. I will demonstrate each of these techniques in the subsections that follow. (The sample solution for this section is the CustomAttributeDemo.sln file.)
The custom attribute constructed in this example facilitates the creation of a user interface generator tool. The basic idea is that a user interface generator ( UserInterfaceGenerator.csproj ) reads the properties of a type and generates controls at runtime for that type. The ControlHintAttribute class defined below allows a programmer to provide a hint indicating what kind of control the generator should create.
Specifying Attribute Usage
Listing 5.7 shows the complete custom attribute listing to provide a control hint to a user interface generator. I will refer to that code throughout the remainder of this section. The example in Listing 5.7 is provided in the CustomAttribute.vbproj example project.
Listing 5.7 Creating a Complete Custom Attribute
1: <AttributeUsage(AttributeTargets.Field Or AttributeTargets.Property, _ 2: AllowMultiple:=False)> _ 3: Public Class ControlHintAttribute 4: Inherits System.Attribute 5: 6: Private FControlType As Type 7: Private FForce As Boolean 8: Private FDescription As String 9: 10: Public Sub New(ByVal ControlType As Type) 11: FControlType = ControlType 12: End Sub 13: 14: Public ReadOnly Property ControlType() As Type 15: Get 16: Return FControlType 17: End Get 18: End Property 19: 20: Public Property Force() As Boolean 21: Get 22: Return FForce 23: End Get 24: Set(ByVal Value As Boolean) 25: FForce = Value 26: End Set 27: End Property 28: 29: Public Property Description() As String 30: Get 31: Return FDescription 32: End Get 33: Set(ByVal Value As String) 34: FDescription = Value 35: End Set 36: End Property 37: 38: End Class
The first piece of the custom attribute puzzle is contained in lines 1 and 2 of Listing 5.7. We need to apply the AttributeUsageAttribute to the custom attribute. The AttributeUsageAttribute is used to provide additional information about our custom attribute. The first argument, AttributeTargets , is a named argument that indicates the kind of entities this particular attribute is valid on. Line 1 indicates that our custom attribute will be valid on ”that is, we can apply it to ”fields and properties only. (Several other possible values can be referenced; look up the AttributeTargets enumerated type in the .NET help documentation.) This positional argument to the constructor represents the ValidOn property of the AttributeUsageAttribute class.
We also can specify the named arguments AllowMultiple and Inherited . AllowMultiple is False by default (it is redundant to list the named argument if setting the default value) and Inherited is True by default. The AllowMultiple named argument indicates whether or not we can apply the custom attribute to the same entity more than one time. The Inherited named argument indicates whether or not our attribute is inherited by derived classes or overridden members . Based on the application of the AttributeUsageAttribute in Listing 5.7, our attribute can be applied only one time to any particular entity and will be inherited.
Inheriting from the Attribute Class
The second piece of creating a custom attribute is to inherit from the System.Attribute class. This step, shown in line 4 of Listing 5.7, speaks for itself. There are no members of System.Attribute that we must override in our custom attribute. Simply add the inheritance statement as demonstrated.
Defining Positional Arguments
An argument is positional when it is initialized as a parameter to the constructor. You will provide values for positional arguments first and in the order that they appear. Generally the value of a positional argument is implemented as a ReadOnly property in the custom attribute class. You can see this reflected in the Sub New constructor in Listing 5.7. Line 10 defines a positional argument named ControlType , which is in turn available as a ReadOnly property in lines 14 through 18.
Defining Named Arguments
Named arguments are passed to the constructor by indicating the property name followed by the := operator and a suitable value for the named argument. Listing 5.7 defines two possible named arguments: Force and Description . The idea in this instance is that Force is used to indicate that ControlType is the type of control that should be created, and Description allows the consumer to provide some descriptive text. You are not required to provide a value for the named arguments, but you must provide values for positional arguments.
The balance of the code is very straightforward. By examining the code in Listing 5.7 it is apparent that we are basically setting class properties in ControlHintAttribute . The next section demonstrates how we can use Reflection in our applications to explore this information.