Chapter 8: Attributes

In Part One of this three-part excerpt from O'Reilly's VB .NET Language in a Nutshell, the authors give an introduction to Attributes and its syntax and use.

Attributes are declarative tags that can be used to annotate types or class members, thereby modifying their meaning or customizing their behavior. This descriptive information provided by the attribute is stored as metadata in a .NET assembly and can be extracted either at design time or at runtime using reflection.

To see how attributes might be used, consider the <WebMethod> attribute, which might appear in code as follows:

<WebMethod(Description:="Indicates the number of visitors to a page")> _
Public Function PageHitCount(strULR As String) As Integer

Ordinarily, public methods of a class can be invoked locally from an instance of that class; they are not treated as members of a web service. In contrast, the <WebMethod> attribute marks a method as a function callable over the Internet as part of a web service. This <WebMethod> attribute also includes a single property, Description, which provides the text that will appear in the page describing the web service.

You may wonder why attributes are used on the .NET platform and why they are not simply implemented as language elements. The answer comes from the fact that attributes are stored as metadata in an assembly, rather than as part of its executable code. As an item of metadata, the attribute describes the program element to which it applies and is available through reflection both at design time (if a graphical environment such as Visual Studio .NET is used), at compile time (when the compiler can use it to modify, customize, or extend the compiler's basic operation), and at runtime (when it can be used by the Common Language Runtime or by other executable code to modify the code's ordinary runtime behavior).

The behavior of interface objects (i.e., controls) in Visual Studio .NET illustrates the importance of attributes. Since Visual Studio offers drag-and-drop placement of controls on forms or web pages, it is necessary for controls to have a design time behavior in addition to their runtime behavior. For instance, when you double click on a control in a designer, you ordinarily want the code or the code template for its default event handler to be displayed. Note that the question posed here is not how the control should respond to a double-click event, since the DoubleClick event occurs at runtime and, if an event handler is present, causes that event handler's executable code to be executed. Because we're concerned with the standard behavior of a control in its design time environment, an attribute provides an excellent solution. Indeed, the .NET Framework provides the <DefaultEvent> attribute, which allows you to define a control's default event. Since information on the attribute is stored in the assembly's metadata, Visual Studio can simply look to see whether a <DefaultEvent> attribute is attached to a particular control when it is double-clicked in a designer window.

The attribute-based system of programming implemented in .NET is extensible. In addition to the attributes predefined by Visual Basic or by the .NET Framework, you can define custom attributes that you apply to program elements. For an attribute to be meaningful, there must also be code that attempts to detect the presence of the attribute at design time, at compile time, or at runtime, and accordingly that performs an action dictated by the attribute's presence.

This chapter discusses the syntax and use of attributes, and then shows how to define and use custom attributes.

Syntax and Use

In Visual Basic, an attribute appears within angle brackets (a less-than (<) and a greater-than symbol (>)). The attribute name is followed by parentheses, which are used to enclose arguments that might be passed to the attribute. For example, the <Obsolete> attribute marks a type or type member as obsolete. We can apply <Obsolete> as a parameter-less attribute as follows:

<Obsolete( )>

If no arguments are assigned to the attribute, we can omit the trailing parentheses:

<Obsolete>

If more than one attribute is applied to a single program element, the attributes are enclosed in a single set of angle brackets and delimited from one another by a comma. For example:

Each attribute corresponds to a class derived from System.Attribute. (In fact, the VB.NET compiler actually treats an attribute as an instance of the attribute's class.) By convention, we drop the trailing string "Attribute" from the class name to form the attribute name, although the attribute name can also be identical to the class name. Thus, for example, the <WebMethod> attribute corresponds to the WebMethodAttribute class in the System.Web.Services namespace, which in turn is found in System.Web.Services.dll. Alternately, you can also specify the attribute as <WebMethodAttribute>. If the namespace containing the attribute class is not automatically accessible to the Visual Basic compiler or to Visual Studio, the Imports directive should be used, and a reference should be added to the project either using the References dialog in Visual Studio or the /r switch in the command-line compiler.

TIP: If the shortened attribute name is a Visual Basic .NET keyword, use an attribute name that's identical to the attribute's class name to prevent a compiler error. For example, the following declaration produces an error because ParamArray is a VB.NET keyword:

<ParamArray( )> lScores As Long)

NOTE: However, the following code compiles correctly:

<ParamArrayAttribute( )> lScores As Long)

The attribute class constructor or constructors determine whether any arguments are required. For example, the <VBFixedString> attribute corresponds to the VBFixedStringAttribute class, which has the following constructor:

New(ByVal Size As Integer)

Hence, the <VBFixedString> attribute can be used as follows:

<VBFixedString(10)> Private sID As String

TIP: Attribute constructors can be overloaded. Any required arguments must correspond to those expected by one of the constructors in number and data type.

Required arguments must be supplied to the attribute as positional arguments only ; named arguments are not accepted. A comma separates all arguments, whether named or positional.

Optional arguments correspond to class properties and can be supplied to the attribute as named arguments. For example, in addition to its constructor, which indicates to what language elements the attribute applies, the <AttributeUsage> attribute, which is used to define the language elements to which a custom attribute applies, has a Boolean property, Inherited, that indicates whether the attribute is inherited by derived classes and overridden members. Its default value is True. To set it to False, you could use the attribute as follows:

Be sure to recognize that attributes are evaluated at compile time, when their data is written to the assembly's metadata. This means that only literal values can be passed as arguments to the attribute's constructor.

Unless it has a modifier, an attribute immediately precedes the language element to which it applies and must be on the same logical line as that language element. If they are on different lines, the Visual Basic .NET line continuation character (the underscore, or _ ) must be used. This syntax is valid for attributes applied to the following language elements:

There are two exceptions to this rule. Some attributes must be prefaced with a modifier (either Assembly: or Module:) indicating the program element to which the attribute applies. In that case, the attribute must be placed at the top of the source file (i.e., immediately following any Option and Imports statements), along with any other attributes that require a modifier. This syntax is valid for an attribute applied to an assembly or a module only.