A custom attribute is a class that inherits, directly or indirectly, from System.Attribute.
When coding custom attributes, the class name should end with the
Attribute word. This is not mandatory but, other than being required by
Microsoft’s Common Language Specification, it provides a better way for
identifying attributes in code. When applying attributes you can shorten
the attribute name excluding the Attribute word. For example, imagine
you have a Document class
representing a simple text document. You might want to provide further
information on the document, such as the author, reviewer, or last edit
date. This information can be provided and stored in the assembly
metadata taking advantage of a custom attribute. Code in Listing 1 shows the implementation of a custom attribute that exposes document properties that is explained next.
Listing 1. Writing a Custom Attribute
<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Property)> Public Class DocumentPropertiesAttribute Inherits Attribute
'Attributes can be inherited 'therefore private fields are Protected Protected _author As String Protected _reviewer As String
Public Overridable ReadOnly Property Author As String Get Return Me._author End Get End Property
Public Overridable ReadOnly Property Reviewer As String Get Return Me._reviewer End Get End Property Public Overridable Property LastEdit As String
Public Sub New(ByVal author As String, ByVal reviewer As String) Me._author = author Me._reviewer = reviewer Me._lastEdit = CStr(Date.Today) End Sub End Class
|
In Visual Basic every custom attribute is a class with Public or Friend access level and decorated with the AttributeUsage
attribute that basically allows specifying what programming elements
can be targeted by the custom attribute. Programming elements are
specified via the System.AttributeTargets
enumeration; the enumeration exposes a number of elements, each of them
self-explanatory about the targeted programming element. For example, AttributeTargets.Class allows applying the attribute to reference types, whereas AttributeTargets.Methods
allows applying the attribute to methods. IntelliSense shows the full
list of the enumeration members, which is straightforward. You notice
that an available member for each element is described in the previous
section for targetable programming elements. AttributeTargets members support bitwise operators so that you combine multiple targets using Or.
Actual metadata is exposed to the external world via properties that
can be either read-only or read/write. Attributes can receive arguments,
although this is not mandatory. For arguments, it is important to
understand how you can ask for required parameters and optional ones.
This is not something that you define as you would usually do in other
programming elements such as methods. Basically required parameters are
specified in the class constructor. Continuing with the example of Listing 1,
our custom attribute requires the specification of the author and the
reviewer of the document, whereas the last edit date is optional and is
still available via a specific property. Optional parameters
initialization is not required; in the mentioned example a default value
for the LastEdit property is supplied. As explained in next subsection, optional arguments are invoked with named parameters.
You should have noticed that the LastEdit property in the custom attribute is of type String instead of type Date. There are some limitations in the applicable data types for attributes parameters. For example, Decimal, Object, and Date are not supported (like structured types as well). Supported types are instead numeric types (Bytes, Short, Integer, Long, Single, and Double), string types (String and Char), enumerations, and the Boolean type. Take care of these limitations that may result in exceptions when passing arguments.
|
There are several other ways
for customizing attributes, but before discovering them here’s how to
apply custom attributes to complete the discussion over parameters.
Applying Custom Attributes
The previous subsection discussed the definition of a custom attribute
for assigning metadata to a class representing a basic text document.
Code in Listing 2 implements the related Document class that is decorated with the DocumentPropertiesAttribute.
Listing 2. Applying Custom Attributes
<DocumentProperties("Alessandro Del Sole", "Robert White", LastEdit:="10/06/2009")> Public Class Document
Public Property Text As String
Public ReadOnly Property Length As Integer Get Return Text.Length End Get End Property
<DocumentProperties("Alessandro Del Sole", "Stephen Green")> Public Property DocumentName As String
Public Sub SaveDocument(ByVal fileName As String) '... End Sub
Public Sub LoadDocument(ByVal filneName As String) '... End Sub End Class
|
When you apply an attribute, you can shorten its name by excluding the Attribute word in the identifier. For example, DocumentPropertiesAttribute can be shortened as DocumentProperties.
The Visual Basic compiler correctly recognizes the identifier of an
attribute. Then you must provide required arguments, respecting the data
type. Such arguments are defined in the constructor of the attribute
definition (see the previous subsection). If you want to also specify an
optional argument, such as the LastEdit one in the previous example, you need to perform it via a named parameter. Named parameters are literals followed by the :=
symbols and by information of the required type. This is the only way
for providing optional arguments. Notice also how the custom attribute
is applied at both class and property level; this is allowed by the
attribute definition. Attributes are therefore useful for providing
additional information that will be stored in the assembly metadata, to custom objects. Attributes are flexible for other reasons that are covered in next sections.
Applying Attributes Multiple Times
According to the
particular nature of your custom attributes, you can decide whether
multiple instances can be applied to programming elements. This is
accomplished by setting the AllowMultiple property as True in the AttributeUsage. The following is an example:
<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Property,
AllowMultiple:=True)>
Public Class DocumentPropertiesAttribute
Inherits Attribute
Notice that AllowMultiple
is optional and thus is invoked as a named parameter. The following is
an example on how you apply multiple instances of an attribute:
<DocumentProperties("Alessandro Del Sole",
"Stephen Green")>
<DocumentProperties("Alessandro", "Stephen",
LastEdit:="10/07/2009")>
Public Property DocumentName As String
In the particular example of the DocumentProperties attribute, multiple instances probably do not make much sense, but this is the way for applying them.
Defining Inheritance
There are situations where
you create classes that inherit from other classes that are decorated
with attributes. Attribute inheritance is not automatic in that you can
establish whether your attributes are inheritable. You establish this
behavior by setting the Inherited property at AttributeUsage level. By default, if you do not explicitly set Inherited, it is considered as True. The following example shows how you enable attribute inheritance:
'Attribute is also inherited
<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Property,
Inherited:=True)>
Public Class DocumentPropertiesAttribute
The following snippet shows instead how to make an attribute not inheritable:
'Attribute is not inherited
<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Property,
Inherited:=False)>
Public Class DocumentPropertiesAttribute
Inheritance is enabled by
default because if a base class is decorated with attributes, derived
classes probably also need them. Because of this, you should be careful
when disabling inheritance. Code in Listing 3
shows an example about declaring two attributes with inheritance
definitions and how a derived type is influenced by attribute
inheritance.
Listing 3. Conditioning Attribute Inheritance
<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Method, Inherited:=False)> Public Class FirstAttribute Inherits Attribute
'Implement your code here.. End Class
<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Method)> Public Class SecondAttribute Inherits Attribute
'Implement your code here.. End Class
Public Class Person Public Property LastName As String Public Property FirstName As String
'The base class takes both attributes <First(), Second()> Public Overridable Function FullName() As String Return String.Concat(LastName, " ", FirstName) End Function End Class
Public Class Contact Inherits Person
'This derived class takes only the Second attribute 'because First is marked as Inherited:=False Public Overrides Function FullName() As String Return MyBase.FullName() End Function End Class
|
Notice how the FullName method in the Contact class inherits just the Second attribute appliance whereas the First attribute is not applied because of inheritance settings.