Chapter 6 introduced the concept of attributes, which have already appeared in several examples. In this chapter we used the Serializable and Synchronization attributes, which are provided by .NET Framework classes. The .NET Framework makes the attribute mechanism entirely extensible, allowing you to define custom attributes, which can be added to a class's metadata. This custom metadata is available through reflection and can be used at runtime. To simplify the use of custom attributes, you may declare a base class to do the work of invoking the reflection API to obtain the metadata information. The example CustomAttribute illustrates the custom attribute InitialDirectory . InitialDirectory controls the initial current directory where the program runs. By default, the current directory is the directory containing the program's executable. In the case of a Visual Studio .NET project built in Debug mode, this directory is \ bin , relative to the project source code directory. Using a Custom Attribute Before we discuss implementing the custom attribute, let us look at how the InitialDirectory attribute is used. To be able to control the initial directory for a class, we derive the class from the base class DirectoryContext . We may then apply to a class the attribute InitialDirectory , which takes a String parameter giving a path to what the initial directory should be. The property DirectoryPath extracts the path from the metadata. If our class does not have the attribute applied, this path will be the default. Here is the code for our test program. When you run this sample on your system, you can change the directory in the attribute to any directory that exists on your machine. ' CustomAttribute.vb Imports System Imports System.IO Class Normal Inherits DirectoryContext End Class <InitialDirectory("c:\")> Class Special Inherits DirectoryContext End Class Public Module AttributeDemo Public Sub Main() Dim objNormal As Normal = New Normal() Console.WriteLine(_ "path = {0}", objNormal.DirectoryPath) ShowDirectoryContents(objNormal.DirectoryPath) Dim objSpecial As Special = New Special() Console.WriteLine(_ "path = {0}", objSpecial.DirectoryPath) ShowDirectoryContents(objSpecial.DirectoryPath) End Sub Private Sub ShowDirectoryContents(ByVal path As String) Dim dir As DirectoryInfo = New DirectoryInfo(path) Dim files() As FileInfo = dir.GetFiles() Console.WriteLine("Files:") Dim f As FileInfo For Each f In files Console.WriteLine(" {0}", f.Name) Next Dim dirs() As DirectoryInfo = dir.GetDirectories() Console.WriteLine("Directories:") Dim d As DirectoryInfo For Each d In dirs Console.WriteLine(" {0}", d.Name) Next End Sub End Module Here is the output: path = C:\OI\NetVB\Chap10\CustomAttribute\bin Files: CustomAttribute.exe CustomAttribute.pdb Directories: path = c:\ Files: BOOTLOG.TXT MSDOS.SYS DETLOG.TXT BOOTLOG.PRV OAKCDROM.SYS AUTOEXEC.BAT SETUPLOG.TXT VIDEOROM.BIN LOGO.SYS command.com SUHDLOG.DAT NETLOG.TXT CONFIG.BAK Defining an Attribute Class To create a custom attribute, you must define an attribute class derived from the base class Attribute . By convention, you give your class a name ending in "Attribute." The name of your class without the "Attribute" suffix will be the name of the custom attribute. In our example the class name is InitialDirectoryAttribute , so the attribute's name is InitialDirectory . You may provide one or more constructors for your attribute class. The constructors define how to pass positional parameters to the attribute (provide a parameter list, separated by commas). It is also possible to provide "named parameters" for a custom attribute, where the parameter information will be passed using the syntax name = value. You may also provide properties to read the parameter information. In our example, we have a property Path , which is initialized in the constructor. <AttributeUsage(AttributeTargets.Class)> _ Public Class InitialDirectoryAttribute Inherits Attribute Private m_Path As String Public Sub New(ByVal path As String) Me.m_Path = path End Sub Public ReadOnly Property Path() As String Get Return m_Path End Get End Property End Class Defining a Base Class The last step in working with a custom attribute is to provide a means to extract the custom attribute information from the metadata, using the reflection classes. You can obtain the Type of any object by calling the method GetType , which is provided in the root class Object . Using the class's method GetCustomAttributes , you can read the custom attribute information. To make the coding of the client program as simple as possible, it is often useful to provide a base class that does the work of reading the custom attribute information. [10] We provide a base class DirectoryContext , which is used by a class wishing to take advantage of the InitialDirectory attribute. This base class provides the property DirectoryPath to return the path information stored in the metadata. Here is the code for the base class: [10] With single implementation inheritance, there is a cost to providing a base class. If you need to derive from another class, such as ContextBoundObject , the base class has to derive from that class. ' DirectoryContext.cs Imports System Imports System.Reflection Imports System.IO Public Class DirectoryContext Public Overridable ReadOnly Property DirectoryPath () _ As String Get Dim t As Type = Me.GetType() Dim a As Attribute For Each a In t.GetCustomAttributes(True) Dim da As InitialDirectoryAttribute = a If Not da Is Nothing Then Return da.Path End If Next Return Directory.GetCurrentDirectory() End Get End Property End Class We must import the System.Reflection namespace. GetType returns the current Type object, and we can then use the GetCustomAttributes method can obtain a collection of Attribute objects from the metadata. Since this collection is heterogeneous, consisting of different types, the result of the VB.NET assignment operator is tested against Nothing in an If statement. This is used to test if a given collection element is of the type InitialDirecto ryAttribute . If we find such an element, we return the Path property. Otherwise, we return the default current directory, obtained from GetCurrentDirectory . |