oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button O'Reilly Book Excerpts: VB .NET Language in a Nutshell


by Steven Roman, Ron Petrusha, Paul Lomax

Using a Custom Attribute

This is the third and final part of this excerpt from O'Reilly's VB .NET Language in a Nutshell, Chapter 8, "Attributes." This article focuses on custom attributes.

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.

TIP: 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.

Previously in the Series

An Introduction to VB.NET Attributes, Part 2

An Introduction to VB.NET Attributes, Part 1

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 = _
   ' 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)
      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)
         End If
      End If
   ' Enumerate types
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
         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)
         ' No need to do anything
      End Try
   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, _
      oTypes = oAssem.GetTypes(  )
      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
         If strTypeAttr <> "" Then
         End If
         If strMeth <> "" Then
            Console.WriteLine(strMeth & vbCrLf)
         End If
      End If
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
            strMeth = oMemberInfo.Name & strMeth
         End If
         strMeth = strMeth & vbCrLf & DisplayDeveloperNotes(oAttribs) _
                   & vbCrLf
         strRetVal = strRetVal & strMeth 
      End If
   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.

Related Reading

VB.NET Language in a Nutshell
By Steven Roman, Ron Petrusha, Paul Lomax

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 = _

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.

For more information on VB.NET in a Nutshell, visit the catalog page.