Section 9.3. Using a Custom Attribute


9.3. Using a Custom Attribute

The attribute classes included with .NET already have specific uses. Visual Studio and the various .NET compilers take specific actions based on these attributes, such as making a method available on the Internet through the <WebMethod> attribute. But no code yet exists to use the <DeveloperNote> attribute designed above. Not only must you define the custom attribute, you must also develop routines that will identify the attribute and take action as needed.

All attribute properties are stored in .NET assemblies as metadata. This metadata can be 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 its greater accessibility through the .NET Framework's reflection features, assembly metadata is always stored with the assembly. Although COM type libraries can be stored in the EXE or DLL files containing the COM objects, they are most commonly stored in separate files (such as ".tlb" files) that are distinct from the COM objects they describe.


The .NET Framework provides support for reflection through the System.Type class and through the features found in the System.Reflection namespace. The following code creates a console mode application that uses reflection to extract <DeveloperNote> attribute details from an assembly that uses this custom attribute.

     Option Strict On     Imports Microsoft.VisualBasic     Imports System     Imports System.Reflection     Imports System.Text     ' ----- We placed the <DeveloperNote> attribute in     '   the Extensions.CustomAttributes namespace.     Imports Extensions.CustomAttributes     Module modComments     Public Sub Main(  )        ' ----- Report on <DeveloperNote> use in an assembly.        Dim fileToExamine As String        Dim outputText As String        Dim assemblyView As System.Reflection.Assembly        Dim attributeSet(  ) As Attribute        Dim moduleScan As System.Reflection.Module        Dim moduleSet(  ) As System.Reflection.Module        ' ----- The assembly to examine comes through the command line.        fileToExamine = Command(  )        If (fileToExamine = "") Then           Console.WriteLine("Syntax is:" & vbCrLf & _              "   DevNotes <filename>")           Exit Sub        End If        ' ----- Load the assembly through reflection.        assemblyView = Reflection.Assembly.LoadFrom(fileToExamine)        ' ----- Output information on assembly-level attributes.        attributeSet = Attribute.GetCustomAttributes(assemblyView)        If (attributeSet.Length > 0) Then           outputText = PrepareDeveloperNotes(attributeSet)           If (outputText <> "") Then              Console.WriteLine(assemblyView.GetName.Name & _                 " Assembly Developer Notes:")              Console.WriteLine(outputText)           End If        End If        ' ----- Output information on module-level attributes.        moduleSet = assemblyView.GetModules(  )        For Each moduleScan In moduleSet           attributeSet = Attribute.GetCustomAttributes(moduleScan)           If (attributeSet.Length > 0) Then              outputText = PrepareDeveloperNotes(attributeSet)              If (outputText <> "") Then                 Console.WriteLine(moduleScan.Name & _                    " Module Developer Notes:)                 Console.WriteLine(outputText)              End If           End If        Next moduleScan        ' ----- Output information on type-level attributes.        EnumerateTypes(assemblyView)     End Sub     Public Function PrepareDeveloperNotes(attributeSet(  ) As Object) _           As String        ' ----- Format information about each attribute.        Dim msg As New StringBuilder        Dim attributeScan As Attribute        Dim noteEntry As DeveloperNoteAttribute        On Error Resume Next        ' ----- Build the notes.        For Each attributeScan In attributeSet           If (TypeOf (attributeScan) Is DeveloperNoteAttribute) Then              noteEntry = CType(attributeScan, DeveloperNoteAttribute)              msg.Append("  Developer: " & noteEntry.Name & vbCrLf)              msg.Append("  Comment: " & noteEntry.Comment & vbCrLf)              msg.Append("  Date: " & noteEntry.DateRecorded & vbCrLf)              msg.Append("  Bug: " & noteEntry.Bug & vbCrLf)           End If        Next attributeScan        ' ----- Return the results as an ordinary string.        Return msg.ToString     End Function     Private Sub EnumerateTypes(assemblyView As Reflection.Assembly)        ' ----- Process each type in the entire assembly.        Dim typeScan As Type        Dim typeSet(  ) As Type        Dim typeCategory As String        Dim attributeSet(  ) As Object        Dim attributeMsg As String        Dim methodMsg As String        ' ----- Retrieve the types for this assembly.        typeSet = assemblyView.GetTypes(  )        ' ----- Get a friendly name for the type category.        For Each typeScan In typeSet           If typeScan.IsClass Then              typeCategory = "Class"           ElseIf typeScan.IsValueType Then              typeCategory = "Structure"           ElseIf typeScan.IsInterface Then              typeCategory = "Interface"           ElseIf typeScan.IsEnum Then              typeCategory = "Enum"           Else              typeCategory = ".NET Type"           End If           ' ----- Get any type-level attributes.           attributeSet = typeScan.GetCustomAttributes(False)           If (attributeSet.Length > 0) Then              attributeMsg = PrepareDeveloperNotes(attributeSet)           Else              attributeMsg = ""           End If           ' ----- Get the details for this type's members.           methodMsg = EnumerateTypeMembers(typeScan)           ' ----- Display any collected information, if available.           If (methodMsg <> "") Or (attributeMsg <> "") Then              Console.WriteLine(typeCategory & " " & typeScan.Name & ":")              If (attributeMsg <> "") Then _                 Console.WriteLine(attributeMsg)              If (methodMsg <> "") Then _                 Console.WriteLine(methodMsg)           End If        Next typeScan     End Sub     Private Function EnumerateTypeMembers(typeEntry As Type) As String        Dim memberInfo As String        Dim fullInfo As String = ""        Dim noteDetails As String        Dim attributeSet(  ) As Object        Dim memberScan As MemberInfo        Dim memberSet(  ) As MemberInfo        ' ----- Get members of the type.        memberSet = typeEntry.GetMembers        For Each memberScan In memberSet           ' ----- Determine if any attributes are present.           attributeSet = memberScan.GetCustomAttributes(False)           If (attributeSet.Length > 0) Then              ' ----- Determine the member type.              Select Case memberScan.MemberType                 Case MemberTypes.All                    memberInfo = " All"                 Case MemberTypes.Constructor                    memberInfo = " Constructor"                 Case MemberTypes.Custom                    memberInfo = " Custom method"                 Case MemberTypes.Event                    memberInfo = " Event"                 Case MemberTypes.Field                    memberInfo = " Field"                 Case MemberTypes.Method                    memberInfo = " Method"                 Case MemberTypes.NestedType                    memberInfo = " Nested type"                 Case MemberTypes.Property                    memberInfo = " Property"                 Case MemberTypes.TypeInfo                    memberInfo = " TypeInfo"                 Case Else                    memberInfo = " Member"              End Select              ' ----- Add in the name of the member.              If (memberScan.Name = ".ctor") Then                 ' ----- Constructor.                 memberInfo = "New" & memberInfo              Else                 memberInfo = memberScan.Name & memberInfo              End If              ' ----- Get the note details.              noteDetails = PrepareDeveloperNotes(attributeSet)              If (noteDetails <> "") Then _                 fullInfo &= memberInfo & vbCrLf & noteDetails & vbCrLf           End If        Next memberScan        ' ----- Fully formatted and ready to use.        Return fullInfo     End Function     End Module 

This program scans an assembly, examining almost everything that can have attributes attached. If it finds attributes attached to an item, it loops through them looking for any that use the <DeveloperNote> attribute. If a match is found, it prints the name of the item and the related developer note details to the console.

The program's entry point, Main, first instantiates an Assembly object (from the System.Reflection namespace) representing the assembly identified on the command line. It then calls the System.Attribute's shared GetCustomAttributes method to obtain any attributes associated with the assembly itself. If any exist, they are passed to the PrepareDeveloperNotes method, which looks specifically for DeveloperNoteAttribute entries and formats them for printing.

Back in Main, the same process is done for each module contained within the assembly by calling the assembly's GetModules method.

The final task displays the developer notes for each type in the assembly. Since the logic is somewhat different, it's all done in the EnumerateTypes routine. This routine gets all the types for the entire assembly through the assembly's GetTypes method. Then it scans each type, checking for associated attributes. If it finds them, it documents any developer notes (once again through the PrepareDeveloperNotes function).

Since each type contains members that, in turn, can have <DeveloperNote> attributes, those are displayed as well through the EnumerateTypeMembers routine. This routine is not that different in its overall structure from the EnumerateTypes routine, but there is some interesting code used in the formatting of the member name. If the memberScan object represents a constructor, ".ctor" is used for the member name. The routine converts this to the more user-friendly "New."

The program provides a good overview of attribute analysis, although it could be enhanced even more. The EnumerateTypes routine could be made a little more generic, and recursion added, allowing it to display attribute information found in nested classes. Another reasonable enhancement would display developer notes associated with parameters belonging to individual methods.




Visual Basic 2005(c) In a Nutshell
Visual Basic 2005 in a Nutshell (In a Nutshell (OReilly))
ISBN: 059610152X
EAN: 2147483647
Year: 2004
Pages: 712

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