Historically developers wrote applications and created additional kinds of supporting data for those applications. The supporting data came in the form of custom data files, .INI files, database tables, and the registry. This supporting data was essential for the proper function of the application. More recently the modus operandi was to register applications and add supporting data to the registry. Then, the data was retrieved programmatically by using SaveSetting or GetSetting in VB6 or an API that performed a similar function, like GetPrivateProfileString. The deficit that existed is that the application and the application’s supporting information, or metadata, traveled in separate containers. VB.NET keeps its metadata with the application in a unit referred to as an assembly. An assembly contains an assembly manifest, metadata, and a portable executable, the application.
It is this combination of metadata and executable together in an assembly that make it possible for xcopy deployment of VB.NET applications. Attributes are classes derived from System.Attribute that allow developers to describe the metadata. There are attributes for every conceivable type in .Net, including assembly level, class, procedure, enumeration, and property attributes to name a few. An additional benefit of the attribute idiom is that you can implement custom attributes and effectively extend Visual Studio .NET without actually modifying or even having access to the Visual Studio code itself.
In this article we will examine how to apply attributes and how to initialize attribute objects using positional and named arguments.
It is important to keep in mind that attributes are classes. While their application is slightly different then writing everyday statements, attributes are not much harder to use than any other idiom and they can just as easily be extended as any other type.
By convention attribute classes are subclassed from System.Attribute and have an attribute suffix. Also, by convention the attribute suffix is not used when the attribute is applied but you can use the suffix if you want to. The syntax for attributes is to place the name of the Attribute class inside of the <> brackets followed by parentheses.
The class myattribute represents a class that inherits from System.Attribute. (This is similar to a C++ or C# constructor call.) You may also include the namespace if the namespace containing the attribute has not been imported. Following the attribute tag is the code element that you are applying the attribute to. For example, the DebuggerHiddenAttribute inhibits the debugger from stepping into methods that have this attribute applied. DebuggerHiddenAttribute is defined in the System.Diagnostics namespace. The following example demonstrates the abbreviated form of the DebuggerHiddenAttribute applied to an event handler followed by the verbose form.
<DebuggerHidden()> _ Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click MsgBox("Can't step into this procedure!") End Sub
<System.Diagnostics.DebuggerHiddenAttribute()> _ Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click MsgBox("Can't step into this procedure!") End Sub
(Note the continuation character after the attribute.) Both examples apply the DebuggerHiddenAttribute to the method Button1_Click. Event if you were to place a breakpoint on the MsgBox statement the debugger would not allow you to step into the source code, although you could step into the disassembled code.
Passing Arguments to Attributes
An attribute clause is essentially a call to an attribute class’ constructor, passing parameters to that constructor. In addition, you may pass the value of properties—referred to as named arguments—between the parentheses of the attribute clause.
Positional arguments represent parameters to the Sub New constructor for the attribute class and are passed in the order they appear in the parameters list. Named arguments represent the name of readable and writable properties defined in the attribute class. Positional arguments are passed like ordinary method parameters and named arguments are passed as name := value pairs.
The System.ComponentModel.DescriptionAttribute is useful for providing description text for properties and events of components. When you select a property in the Properties window (see figure 1) the value of the DescriptionAttribute is displayed in the field at the bottom of the Properties window as shown in the figure.
Figure 1: The result of applying the System.ComponentModel.DescriptionAttribute to a component property.
The Text property and its incumbent description text “The text contained in the control” is shown in figure 1. The DescriptionAttribute takes a single positional argument, description. Suppose we implemented a custom UserControl with a Color property. We could add a Description to the Color property to indicate to the user what the Color property was used for.
Imports System.ComponentModel Public Class UserControl1 Inherits System.Windows.Forms.UserControl [ Windows Form Designer generated code ] <Description("Defines the control's foreground color")> _ Public Property Color() As String Get End Get Set(ByVal Value As String) End Set End Property End Class
The DescriptionAttribute would initialize the underlying field representing the description text with the positional argument passed as literal text to the DescriptionAttribute constructor.
The AttributeUsageAttribute is an example of an attribute that uses both positional and named arguments. AttributeUsageAttribute is applied to classes that generalize, or inherit from, the System.Attribute class. AttributeUsageAttribute one positional and two possible named arguments. (You are not required to supply values for named arguments.) The positional argument is validOn which is one or more of the AttributeTargets enumerated values. For example, passing AttributeTargets.All to the AttributeUsageAttribute indicates that a custom attribute is applicable to all code elements.
The named arguments for the AttributeUsageAttribute are AllowMultiple and Inherited. AllowMultiple is a named Boolean argument that indicates whether an attribute can be applied to the same element more than once. AllowMultiple is False by default. Attributes where AllowMultiple is False are referred to as single use attributes. When AllowMultiple is True such attributes are referred to as multiuse attributes.
The Inherited Boolean, named argument is True by default. Inherited indicates whether or not the attribute is inherited by derived classes. Applying the AttributeUsageAttribute indicating that a custom attribute could be applied to all code elements and that the attribute could be applied more than once to the same code element, the attribute clause would take the following form:
Look into the VS.NET help system for more on attributes. Attributes can be applied to all code elements, and you can implement custom elements by subclassing from an existing attribute class.
Attributes are classes that allow individual developers to extend the VS.NET tools without rebuilding VS.NET each time. There are many attributes are already available for you to explore, but most of the interesting ones haven’t even been written yet. Perhaps some of those will be implemented by you.
About the Author
Paul Kimmel is a freelance writer for Developer.com and CodeGuru.com. Look for cool Visual Basic .Net topics in his upcoming book Sams Visual Basic .Net Unleashed.
Paul founded Software Conceptions, Inc. in 1990. Contact Paul Kimmel at [email protected] for help building VB.NET applications or migrating VB6 applications to .NET.