Section 8.3. Using a Custom Attribute

   

8.3 Using a Custom Attribute

The Visual Basic compiler and .NET platform automatically recognize the meaning of the attributes based on attribute classes in the .NET Framework Class Library. This recognition isn't true, however, for custom attributes. Thus, not only must you define them, you must also develop a set of routines that will identify the presence of an attribute so your code can handle them.

NET assemblies are self-describing ; when the compiler creates the .NET assembly, it writes metadata describing the assembly and its classes and methods to the assembly manifest. This metadata is then accessed programmatically at runtime by using the .NET Framework's reflection classes.

An assembly's metadata is similar to a COM type library. In addition to their greater accessibility through .NET Framework APIs, assembly metadata is always stored along with the assembly. In contrast, although a type library can be stored in the EXE or DLL containing the COM object (as did previous versions of Visual Basic), it is most commonly stored in a file different from the file containing the COM objects it describes.

The .NET Framework provides support for reflection in the Type class (in the System namespace) and in the types found in the System.Reflection namespace. The following code creates a console mode application that uses the reflection classes to extract information about the <DeveloperNote> custom attribute and the program elements to which it is applied:

 Option Strict On Imports Microsoft.VisualBasic Imports System Imports System.Reflection Imports System.Text Imports Extensions.CustomAttributes Module modComments Public Sub Main(  )    Dim strFile As String = Command(  )    Dim sOutput As String    If strFile = "" Then        Console.WriteLine("Syntax is: " & vbCrLf & _                         "   DevNotes <filename>")       Exit Sub    End If    ' Load assembly    Dim oAssem As System.Reflection.Assembly = _                  System.Reflection.Assembly.LoadFrom(strFile)    ' Get any assembly-level attributes    Dim oAttribs(  ) As Attribute = Attribute.GetCustomAttributes(oAssem)    if UBound(oAttribs) >= 0 Then       sOutput = DisplayDeveloperNotes(oAttribs)       if sOutput <> "" Then          Console.WriteLine(oAssem.GetName.Name & _                            " Assembly Developer Notes:" & vbCrLf)          Console.WriteLine(sOutput)       End If    End If    ' Get any module-level attributes    Dim oMod As System.Reflection.Module    Dim oMods() As System.Reflection.Module = oAssem.GetModules(  )    For Each oMod in oMods       oAttribs = Attribute.GetCustomAttributes(oMod)       If UBound(oAttribs) >= 0 Then          sOutput = DisplayDeveloperNotes(oAttribs)          If sOutput <> "" Then             Console.WriteLine(oMod.Name & " Module Developer Notes: " _                               & vbCrLf)             Console.WriteLine(sOutput)          End If       End If    Next    ' Enumerate types    EnumerateTypes(oAssem) End Sub ' Show information about each attribute Public Function DisplayDeveloperNotes(oAttribs(  ) As Object) As String    Dim sMsg As New StringBuilder    Dim oAttrib As Attribute    Dim oNote As DeveloperNoteAttribute    For Each oAttrib in oAttribs       Try          oNote = CType(oAttrib, DeveloperNoteAttribute)          sMsg.Append("  Developer: " & oNote.Name & vbCrLf)          sMsg.Append("  Comment: " & oNote.Comment & vbCrLf)          sMsg.Append("  Date: " & oNote.DateRecorded & vbCrLf)          sMsg.Append("  Bug: " & oNote.Bug & vbCrLf)       Catch          ' No need to do anything       End Try    Next    Return sMsg.ToString End Function Private Sub EnumerateTypes(oObj As Object)    Dim sOutput As String    Dim oType, oTypes(  ) As Type    If oObj.GetType.ToString = "System.Reflection.Assembly" Then       Dim oAssem As System.Reflection.Assembly = CType(oObj, _                     System.Reflection.Assembly)       oTypes = oAssem.GetTypes(  )    Else       oTypes.SetValue(oObj, 0)    End If    For each oType in oTypes       Dim strType, strTypeAttr, strMeth As String       If oType.IsClass Then           strType = "Class"       ElseIf oType.IsValueType Then          strType = "Structure"       ElseIf oType.IsInterface Then          strType =  "Interface"       ElseIf oType.IsEnum Then          strType = "Enum"       End If       sOutput = strType & " " & oType.Name & ":" & vbCrLf       ' Get any type-level attributes       Dim oCustAttribs(  ) As Object = oType.GetCustomAttributes(False)       If oCustAttribs.Length > 0 Then          strTypeAttr = DisplayDeveloperNotes(oCustAttribs)       End If       strMeth = EnumerateTypeMembers(oType)       ' Display Type and Member Info       If strMeth <> "" Or strTypeAttr <> "" Then          Console.WriteLine(sOutput)          If strTypeAttr <> "" Then             Console.WriteLine(strTypeAttr)          End If          If strMeth <> "" Then             Console.WriteLine(strMeth & vbCrLf)          End If       End If    Next End Sub Private Function EnumerateTypeMembers(oType As Type) As String    Dim strMeth, strRetVal As String    Dim oAttribs(  ) As Object    ' Get members of type    Dim oMembersInfo(  ), oMemberInfo As MemberInfo    oMembersInfo = oType.GetMembers    For Each oMemberInfo in oMembersInfo       ' Determine if attribute is present       oAttribs = oMemberInfo.GetCustomAttributes(False)       If oAttribs.Length > 0 Then           ' determine member type          Select Case oMemberInfo.MemberType             Case MemberTypes.All                strMeth = " All "               Case MemberTypes.Constructor                strMeth = " Constructor "             Case MemberTypes.Custom                strMeth = " Custom method "               Case MemberTypes.Event                strMeth = " Event "              Case MemberTypes.Field                strMeth = " Field "             Case MemberTypes.Method                strMeth = " Method "             Case MemberTypes.NestedType                strMeth = " Nested type "             Case MemberTypes.Property                strMeth = " Property"              Case MemberTypes.TypeInfo                strMeth = " TypeInfo"          End Select          If oMemberInfo.Name = ".ctor" Then             strMeth = "New " & strMeth          Else             strMeth = oMemberInfo.Name & strMeth          End If          strMeth = strMeth & vbCrLf & DisplayDeveloperNotes(oAttribs) _                    & vbCrLf          strRetVal = strRetVal & strMeth        End If    Next    Return strRetVal End Function End Module 

The program's entry point, the Main routine, first instantiates an Assembly object (in the System.Reflection namespace) representing the assembly by calling the LoadFrom method and passing it the filename containing the assembly. It then calls the Attribute class' shared GetCustomAttributes method, passing it a reference to an Assembly object, which returns an array of Attribute objects representing each custom attribute, if any exist. These attributes are then displayed by calling the DisplayDeveloperNotes method.

The shared GetCustomAttributes method of the Attribute class has several overloads that allow you to retrieve custom attributes belonging to assemblies, modules, class members, and parameters. (Unfortunately, the method does not retrieve the custom attributes belonging to types.) Since derived classes call the base class implementation, you can also retrieve attributes of a specific custom type with the following code:

 Dim oAttribs(  ) As Attribute = _     DeveloperNoteAttribute.GetCustomAttributes(oAssem) 

After listing any DeveloperNoteAttributes applied to the assembly, the code retrieves the modules in the assembly by calling the Assembly object's GetModules method, which returns an array of Module objects. The code then iterates these modules and again calls the Attribute class' shared GetCustomAttributes method, this time passing it a Module object (to retrieve an array of custom Attribute objects belonging to that module). These objects are also displayed by calling the DisplayDeveloperNotes method.

Finally, Main calls the EnumerateTypes method, a generic routine that it uses to iterate the types in the Assembly object. (The routine could also be called from a type to extract information about custom attributes in its nested types.) This iteration casts the generic object passed as a parameter to an Assembly object, and then calls the Assembly object's GetTypes method to return an array of Type objects (defined in the System namespace) containing information about each type (such as a class, interface, delegate, structure, or num) in the assembly. Each Type object's GetCustomAttributes method is then called and its custom attributes are displayed.

While iterating the type objects, the EnumerateTypes method also calls the EnumerateTypeMembers method, which is responsible for iterating the members of each type and extracting their custom DeveloperNoteAttribute attributes. The EnumerateTypeMembers method first extracts an array of MemberInfo objects corresponding to each member by calling the GetMembers method of oType , the Type object passed to it as a parameter. GetMembers returns an array of MemberInfo objects, each element of which corresponds to a member of the type. The method then calls the MemberInfo object's GetCustomAttributes method to extract information about any custom types. Instead, it could also have called the Attribute object's GetCustomAttributes method, passing it a MemberInfo object representing the member whose custom attribute information was to be retrieved.

The program can be easily extended by adding recursion (allowing it to retrieve information about custom attributes in a nested class and its members), as well as by retrieving information about custom attributes applied to parameters belonging to individual methods.

   


VB.Net Language in a Nutshell
VB.NET Language in a Nutshell
ISBN: B00006L54Q
EAN: N/A
Year: 2002
Pages: 503

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net