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 |
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.
|
Related Reading
VB.NET Language in a Nutshell |
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.
For more information on VB.NET in a Nutshell, visit the catalog page.
Copyright © 2009 O'Reilly Media, Inc.