An attribute is an object that represents data you want to associate with an element in your program. The element to which you attach an attribute is referred to as the target of that attribute. For example, the attribute:
<NoIDispatch>
is associated with a class or an interface to indicate that the target class should derive from IUnknown rather than IDispatch when exporting to COM.
In Chapter 17, you saw this attribute:
<assembly: AssemblyKeyFile("c:\myStrongName.key")>
This inserts metadata into the assembly to designate the program's StrongName.
Attributes come in two flavors: intrinsic and custom. Intrinsic attributes are supplied as part of the Common Language Runtime (CLR), and they are integrated into .NET. Custom attributes are attributes you create for your own purposes.
Most programmers will use only intrinsic attributes, though custom attributes can be a powerful tool when combined with reflection, described later in this chapter.
If you search through the CLR, you'll find a great many attributes. Some attributes are applied to an assembly, others to a class or interface, and some, such as <WebMethod>, are applied to class members. These are called the attribute targets. Possible attribute targets are detailed in Table 18-1.
Member name |
Usage |
---|---|
All |
Applied to any of the following elements: assembly, class, constructor, delegate, enum, event, field, interface, method, module, parameter, property, return value, or struct |
Assembly |
Applied to the assembly itself |
Class |
Applied to instances of the class |
Constructor |
Applied to a given constructor |
Delegate |
Applied to the delegated method |
Enum |
Applied to an enumeration |
Event |
Applied to an event |
Field |
Applied to a field |
Interface |
Applied to an interface |
Method |
Applied to a method |
Module |
Applied to a single module |
Parameter |
Applied to a parameter of a method |
Property |
Applied to a property (both get and set, if implemented) |
ReturnValue |
Applied to a return value |
Struct |
Applied to a struct |
Apply attributes to their targets by placing them in angle brackets immediately before the target item. You can combine attributes by stacking one on top of another:
<assembly: AssemblyDelaySign(false)> <assembly: AssemblyKeyFile(".\keyFile.snk")>
This can also be done by separating the attributes with commas:
<assembly: AssemblyDelaySign(false), assembly: AssemblyKeyFile(".\keyFile.snk")>
|
Many intrinsic attributes are used for interoperating with COM. You've already seen use of one attribute (<WebMethod>) in Chapter 16. You'll see other attributes, such as the <Serializable> attribute, used in the discussion of serialization in Chapter 19.
The System.Runtime namespace offers a number of intrinsic attributes, including attributes for assemblies (such as the keyname attribute), for configuration (such as debug to indicate the debug build), and for version attributes.
You can organize the intrinsic attributes by how they are used. The principal intrinsic attributes are those used for COM, those used to modify the Interface Definition Language (IDL) file from within a source-code file, those used by the ATL Server classes, and those used by the Visual Basic .NET compiler.
Perhaps the attribute you are most likely to use in your everyday Visual Basic .NET programming (if you are not interacting with COM) is <Serializable>. As you'll see in Chapter 19, all you need to do to ensure that your class can be serialized to disk or to the Internet is add the <Serializable> attribute to the class:
<Serializable> _ Class MySerializableClass
The attribute tag is put in angle brackets immediately before its target—in this case, the class declaration.
The key fact about intrinsic attributes is that you know when you need them; the task will dictate their use.
You are free to create your own custom attributes and use them at runtime as you see fit. Suppose, for example, that your development organization wants to keep track of bug fixes. You already keep a database of all your bugs, but you'd like to tie your bug reports to specific fixes in the code.
You might add comments to your code along the lines of:
// Bug 323 fixed by Jesse Liberty 1/1/2005.
This would make it easy to see in your source code, but there is no enforced connection to Bug 323 in the database. A custom attribute might be just what you need. You would replace your comment with something like this:
<BugFixAttribute(107, "Jesse Liberty", "01/04/05", _ Comment:="Fixed off by one errors")>
You could then write a program to read through the metadata to find these bug-fix notations and update the database. The attribute would serve the purposes of a comment, but would also allow you to retrieve the information programmatically through tools you'd create.
Attributes, like most things in Visual Basic .NET, are embodied in classes. To create a custom attribute, derive your new custom attribute class from System.Attribute:
Public Class BugFixAttribute Inherits System.Attribute
You need to tell the compiler which kinds of elements this attribute can be used with (the attribute target). Specify this with (what else?) an attribute:
<AttributeUsage(AttributeTargets.Class Or _ AttributeTargets.Constructor Or _ AttributeTargets.Field Or _ AttributeTargets.Method Or _ AttributeTargets.Property, AllowMultiple:=True)> _
Notice the line continuation character at the end of the AttributeUsage attribute. In Visual Basic .NET attributes do not stand alone; they must immediately be followed by their target, in this case the class declaration. Thus, the entire statement is:
<AttributeUsage(AttributeTargets.Class Or _ AttributeTargets.Constructor Or _ AttributeTargets.Field Or _ AttributeTargets.Method Or _ AttributeTargets.Property, AllowMultiple:=True)> _ Public Class BugFixAttribute Inherits System.Attribute
AttributeUsage is an attribute applied to attributes: a meta-attribute. It provides, if you will, meta-metadata—that is, data about the metadata. For the AttributeUsage attribute constructor, you pass two arguments. The first argument is a set of flags that indicate the target—in this case, the class and its constructor, fields, methods, and properties. The second argument is a flag that indicates whether a given element might receive more than one such attribute. In this example, AllowMultiple is set to true, indicating that class members can have more than one BugFixAttribute assigned.
The new custom attribute in this example is named BugFixAttribute. The convention is to append the word Attribute to your attribute name. The compiler supports this by allowing you to call the attribute with the shorter version of the name. Thus, you can write:
<BugFix(107, "Jesse Liberty", "01/04/05", _ Comment:="Fixed off by one errors")>
The compiler will first look for an attribute named BugFix and, if it does not find that, will then look for BugFixAttribute.
Every attribute must have at least one constructor. Attributes take two types of parameters: positional and named. In the BugFix example, the bug ID, the programmer's name and the date are positional parameters, and comment is a named parameter. Positional parameters are passed in through the constructor and must be passed in the order declared in the constructor:
Public Sub New( _ ByVal bugID As Integer, _ ByVal programmer As String, _ ByVal theDate As String) mBugID = bugID mProgrammer = programmer mDate = theDate End Sub 'New
Named parameters are implemented as properties:
Public Property Comment( ) As String Get Return mComment End Get Set(ByVal Value As String) mComment = Value End Set End Property
It is common to create read-only properties for the positional parameters:
Public ReadOnly Property BugID( ) As Integer Get Return mBugID End Get End Property
Once you have defined an attribute, you can put it to work by placing it immediately before its target. To test the BugFixAttribute of the preceding example, the following program creates a simple class named MyMath and gives it two functions. Assign BugFixAttributes to the class to record its code-maintenance history:
<BugFixAttribute(121, "Jesse Liberty", "01/03/05"), _ BugFixAttribute(107, "Jesse Liberty", "01/04/05", _ Comment:="Fixed off by one errors")> _ Public Class MyMath
These attributes will be stored with the metadata. Example 18-1 shows the complete program.
Option Strict On Imports System Imports System.Reflection ' create custom attribute to be assigned to class members <AttributeUsage(AttributeTargets.Class Or _ AttributeTargets.Constructor Or _ AttributeTargets.Field Or _ AttributeTargets.Method Or _ AttributeTargets.Property, AllowMultiple:=True)> _ Public Class BugFixAttribute Inherits System.Attribute ' private member data Private mBugID As Integer Private mComment As String Private mDate As String Private mProgrammer As String Public Sub New( _ ByVal bugID As Integer, _ ByVal programmer As String, _ ByVal theDate As String) mBugID = bugID mProgrammer = programmer mDate = theDate End Sub 'New ' accessor Public ReadOnly Property BugID( ) As Integer Get Return mBugID End Get End Property ' property for named parameter Public Property Comment( ) As String Get Return mComment End Get Set(ByVal Value As String) mComment = Value End Set End Property ' accessor Public ReadOnly Property theDate( ) As String Get Return mDate End Get End Property ' accessor Public ReadOnly Property Programmer( ) As String Get Return mProgrammer End Get End Property End Class 'BugFixAttribute ' ********* assign the attributes to the class ******** <BugFixAttribute(121, "Jesse Liberty", "01/03/05"), _ BugFixAttribute(107, "Jesse Liberty", "01/04/05", _ Comment:="Fixed off by one errors")> _ Public Class MyMath Public Function DoFunc1(ByVal param1 As Double) As Double Return param1 + DoFunc2(param1) End Function 'DoFunc1 Public Function DoFunc2(ByVal param1 As Double) As Double Return param1 / 3 End Function 'DoFunc2 End Class 'MyMath Public Class Tester Public Shared Sub Main( ) Dim mm As New MyMath( ) Console.WriteLine("Calling DoFunc(7). Result: {0}", mm.DoFunc1(7)) End Sub 'Main End Class 'Tester Output: Calling DoFunc(7). Result: 9.33333333333333
As you can see, the attributes had absolutely no impact on the output. In fact, for the moment, you have only my word that the attributes exist at all. A quick look at the metadata using ILDasm does reveal that the attributes are in place, however, as shown in Figure 18-1. You'll see how to get at this metadata and use it in your program in the next section.
Top |