7.5 Custom Attributes

only for RuBoard

C# excels over VB as a .NET language in one particular area: XML documentation comments. C# allows you to embed XML comments into your code that can be compiled to documentation independently at a later time. Example 7-6 shows a simple class written in C# that contains XML comments.

Example 7-6. C# class with XML comments
 using System;     /// <summary> /// Summary for the BestLoveSong class. /// </summary> /// <remarks> /// This is a longer description for the BestLoveSong class,  /// which is used as an example for Chapter 7. /// </remarks> public class BestLoveSong {         /// <summary>     /// Name property </summary>     /// <value>     /// Returns the name of the best love song.</value>     public string Name {         get {             return "Feelings";         }     }         /// <summary>     /// Plays the best love song.</summary>     /// <param name="volume"> Volume to play the best love      /// song.</param>     public void Play(byte volume) {         if (volume > 11)             volume = 11;     } } 

You can compile this example from the command line using the /doc switch of the C# compiler as follows :

 C:\>csc /target:library comments.cs /doc:comments.xml 

Using this switch produces an XML file named comments.xml , as shown in Example 7-7.

Example 7-7. XML documentation output
 <?xml version="1.0"?> <doc>     <assembly>         <name>comments</name>     </assembly>     <members>         <member name="T:BestLoveSong">             <summary>             Summary for the BestLoveSong class.             </summary>             <remarks>             This is a longer description for the BestLoveSong              class, which              is used as an example for Chapter 7.             </remarks>         </member>         <member name="M: BestLoveSong.Play(System.Byte)">             <summary>             Plays the best love song.</summary>             <param name="volume">              Volume to play the best love song.             </param>         </member>         <member name="P: BestLoveSong.Name">             <summary>             Name property </summary>             <value>             Returns the name of the best love song.</value>         </member>     </members> </doc> 

This XML can now be processed in a variety of ways to create professional-looking documentation. In fact, this is how the class library documentation for the .NET Framework is created.

Example 7-6 demonstrates only a few of the most important XML comment tags: <summary> , <remarks> , <param> , and <value> . Pay close attention to the <assembly> and <member> tags. They were obtained using reflection and are not a direct part of the XML comments. There are actually quite a few more tags, but there is no time or space available to discuss them all.

Implementing XML comments in VB.NET by using custom attributes is possible. While similar results can be achieved, there is one major drawback to this approach. In C#, the XML tags are comments; they are not stored in the executable. The same cannot be said for attributes, which means that executable size could be significantly larger when using attributes in this manner. However, the degree to which executables expand can be offset greatly by using conditional compilation constants. The attributes might be added only in a debug build, for instance.

There is no doubt that XML comments will be available to VB.NET at some point in the future (if enough people scream for it), so consider this an interim solution. Or just consider it a fun lesson for custom attributes.

7.5.1 Building a Custom Attribute

In addition to deriving from the System.Attribute class, custom attributes use an attribute: AttributeUsage .

AttributeUsage is actually System.AttributeUsageAttribute . From now on, though, attributes will be referred to by their shorthand names . Namespace prefixes and the " Attribute " suffix will not be used when discussing attributes.

AttributeUsage describes three things about the custom attribute that is being created:

  • What class elements can the attribute be applied to?

  • Will derived classes inherit the attributes once they are applied to the base class?

  • Can the attribute be applied to the same class element more than once?

The first question is answered by the AttributeTargets enumeration, which contains all elements that can have attributes applied to them. The members of this enumeration, which are self-explanatory, are as follows: All , Assembly , Class , Constructor , Delegate , Enum , Event , Field , Interface , Method , Module , Parameter , Property , ReturnValue , and Struct .

Here is the basis of an attribute that can be applied to any element:

 Imports System     <AttributeUsage(AttributeTargets.All)> _ Public Class SomeAttribute : Inherits Attribute     End Class 

The target values can also be OR ed together to provide a more granular selection. Here is a custom attribute that can be applied only to a class or a method:

 Imports System     <AttributeUsage(  AttributeTargets.Class Or AttributeTargets.Method  )> _ Public Class SomeAttribute : Inherits Attribute     End Class 

By default, attributes are inherited. However, this behavior can be changed using the Attribute.Inherited property:

 Imports System     <AttributeUsage(AttributeTargets.All,  Inherited:=False  )> _ Public Class SomeAttribute : Inherits Attribute     End Class 

Check out the := operator. When an attribute is applied, the constructor for the attribute class is actually called. For instance, the documentation for AttributeUsage looks like this:

 Public Sub New(ByVal   validOn   As AttributeTargets) 

The constructor has only one parameter, a member of the AttributeTargets enumeration. Inherited is a property of the AttributeUsage class. The := syntax is just a mechanism that allows an instance of an attribute to be created and a property to be set on that instance all in one go.

Attributes can be applied only once per element unless the AllowMultiple property is set to True :

 Imports System     <AttributeUsage(AttributeTargets.All, _                 Inherited:=False, _  AllowMultiple:=True  )> _ Public Class SomeAttribute : Inherits Attribute     End Class 

7.5.2 SummaryAttribute

At this point, the concept of creating a custom attribute might still be nebulous. It's time to remedy that and start building the SummaryAttribute class used to imitate the <summary> comment tag defined by C#.

The process is actually quite simple. First, create a class that is derived from attribute:

 Public Class SummaryAttribute : Inherits Attribute     End Class 

The summary document comment is just a string. The desired behavior is essentially the following:

 <Summary("This class does something really vague")> _ Public Class ThisClass 

The attribute's string argument is actually the single argument passed to the constructor of SummaryAttribute :

 Public Class SummaryAttribute : Inherits Attribute     Public ReadOnly Text As String     Public Sub New(ByVal text As String)         Me.Text = text     End Sub End Class 

A read-only field named Text is added to hold the string. Since the field is read-only, we can make it publicly accessible without difficulty. Later, when using reflection to get the summary attribute, it will be obtained through this field.

Adding the AttributeUsage attribute to the class is the final task. This step is shown in Example 7-8. The attribute can be applied to any element, so the AttributeTargets.All is used.

However, the SummaryAttribute class should not be inherited because this would prevent derived classes from having their own summary. Therefore, Inherited is set to False . Finally, the attribute will be used only once per element, so AllowMultiple is set to False as well.

Example 7-8. SummaryAttribute class
  <AttributeUsage(AttributeTargets.All, _   Inherited:=False, _   AllowMultiple:=False)> _  Public Class SummaryAttribute : Inherits Attribute     Public ReadOnly Text As String     Public Sub New(ByVal text As String)         Me.Text = text     End Sub End Class 

You might wonder why the AllowMultiple property is set to False because of this setting, the attribute is allowed only one application per element. After all, if multiple attributes were allowed, something like this would be possible:

 <Summary("This class is used to demonstate the SummaryAttribute"), _  Summary("class that has been created for Chapter 7."), _  Summary("Unfortunately we cannot use multiple attributes like this")"> _ 

The problem is that attributes are not stored in the assembly in any particular order. It would be nice if they were stored in the order in which they were applied, but that is not the case. Using the summary attribute multiple times would require that we write an alternative sorting method, and possibly that we add an additional parameter in the constructor that specifies the sort order.

This leads to another issue: the formatting of the text stored with the Summary attribute (and the other attributes that will be defined in this chapter). Because the summary is stored as one block of text, a method of formatting it is needed (if, in fact, formatting is necessary).

7.5.3 Additional Documentation Attributes

The remaining documentation attributes are about the same as Summary . The differences lie in where the attributes can be applied. The Remarks attribute is the same as Summary ; it can be applied everywhere. The Param attribute can be applied only to a parameter. The Value attribute can be used only to describe a property's value.

The Param attribute is somewhat different from the others, since it can be applied multiple times. It contains an additional constructor argument that allows a name to be associated with the parameter:

 <Param("key", "This is the key description"), _  Param("entry", "This is the entry description")> _  Public Sub(ByVal key As Integer, ByVal entry As String) 

Example 7-9 shows the code for the remaining attributes. Each class is implemented in basically the same manner; as in the case of exceptions, it is the name of the class that is important.

By distinguishing among the various types of attributes (rather than using the Summary attribute for everything), you can position and format each attribute based on its type.

Example 7-9. Additional documentation attributes
 <AttributeUsage(AttributeTargets.All, Inherited:=False, AllowMultiple:=False)> _ Public Class RemarksAttribute : Inherits Attribute     Public ReadOnly Text As String     Public Sub New(ByVal text As String)         Me.Text = text     End Sub End Class     <AttributeUsage(AttributeTargets.Method Or _                 AttributeTargets.Constructor, _                 Inherited:=False, AllowMultiple:=True)> _ Public Class ParamAttribute : Inherits Attribute     Public ReadOnly Name As String     Public ReadOnly Text As String     Public Sub New(ByVal name As String, ByVal text As String)         Me.Name = name         Me.Text = text     End Sub End Class     <AttributeUsage(AttributeTargets.Property, Inherited:=False, AllowMultiple:=False)> _ Public Class ValueAttribute : Inherits Attribute     Public ReadOnly Text As String     Public Sub New(ByVal text As String)         Me.Text = text     End Sub End Class 

7.5.4 HistoryAttribute

Before using these attributes, we can add one more attribute that is not based on some C# document tag. Since tracking changes to a class throughout the development cycle is useful, an attribute that will help with the process could be very helpful. This attribute can be implemented similarly to the other attributes; it will just contain more parameters in its constructor. It needs a name field to track who made the change, a date field to mark when the change was made, and a text field to hold the description of the change that was made.

Given the specifics of what the attribute needs to contain (and the previous examples), this attribute is trivial to implement. There is nothing to say that has not already been said, which is why the HistoryAtribute class is listed now, in its entirety, in Example 7-10.

Example 7-10. Class history attribute
 <AttributeUsage(AttributeTargets.Class, _                 Inherited:=False, _                 AllowMultiple:=True)> _ Public Class HistoryAttribute : Inherits Attribute     Implements IComparable          Public Author As String     Public [Date] As DateTime     Public Change As String          Public Sub New(ByVal Author As String, _                    ByVal ChangeDate As String, _                    ByVal Change As String)         Me.Author = Author         Me.[Date] = Convert.ToDateTime(ChangeDate)         Me.Change = Change     End Sub          Public Function CompareTo(ByVal obj As Object) As Integer _         Implements IComparable.CompareTo         Dim ha As HistoryAttribute         If TypeOf obj Is HistoryAttribute Then             ha = CType(obj, HistoryAttribute)                 Return DateTime.Compare(Me.Date, ha.Date)         End If     End Function      End Class 

This class also implements IComparable (see Chapter 5), which allows an array of History attributes to be sorted based on the entry date. The implementation checks to make sure the object being passed in is a HistoryAttribute . If it is, the call is delegated to DateTime.Compare , which determines how the dates should be sorted. This will be put to use in the next example.

7.5.5 Custom Attributes at Work

Save all documentation attributes to a file named myattributes.vb and compile it to a class library. Then look at Example 7-11it shows the ServerInfo class from the beginning of the chapter with several custom documentation attributes applied to it. Add the attributes and recompile.

Example 7-11. Custom documentation attributes
 Imports System Imports System.Diagnostics Imports System.Net Imports System.Threading     'Assumes attribute have been compiled to a library  'called myattributes.dll which is referened at compile time '  vbc /t:library /r:system.dll /r:myattributes.dll serverinfo.vb  <Summary("The Amazing ServerInfo Class(tm)"), _  Remarks("This class provides simple machine info."), _  History("JPH", "04/05/2002", _          "Added ability to dump custom attributes"), _  History("JPH", "04/06/2002", _          "Broke all of the code suddenly"), _  History("JPH", "04/07/2002", _          "Fixed everything that I broke.")> _ Public Class ServerInfo         Private machine As String     Private ip As IPAddress         Public Enum Tasks         EnterInfiniteLoop = 1         WasteMemory = 2         Allocate2GigForTheBrowser = 3         RandomlyDestroyProcess = 4     End Enum         Public Event TaskCompleted(ByVal obj As Object, ByVal e As EventArgs)         Public Sub New( )         'Get machine info when object is created         machine = Dns.GetHostName( )         Dim ipHost As IPHostEntry = Dns.GetHostByName(machine)         ip = ipHost.AddressList(0)     End Sub         'This routine only fires an event right now     <Param("task", "The task you want to perform")> _     Public Sub DoTask(ByVal task As Tasks)         RaiseEvent TaskCompleted(Me, EventArgs.Empty)     End Sub         <Summary("Gets date and time on machine")> _     Public Shared Function GetMachineTime( ) As DateTime         Return DateTime.Now     End Function         <Summary("Returns percentage of processor being used")> _     Public Function GetProcessorUsed( ) As Single         If PerformanceCounterCategory.Exists("Processor") Then             Dim pc As New PerformanceCounter("Processor", _                 "% Processor Time", "_Total", True)             Dim sampleA As CounterSample             Dim sampleB As CounterSample                 sampleA = pc.NextSample( )             Thread.Sleep(1000)             sampleB = pc.NextSample( )             Return CounterSample.Calculate(sampleA, sampleB)         End If     End Function         <Summary("Returns available memory is megabytes")> _     Public Function GetAvailableMemory( ) As Long         If PerformanceCounterCategory.Exists("Memory") Then             Dim pc As New PerformanceCounter("Memory", "Available MBytes")             Return pc.RawValue( )         End If         Return 0     End Function         <Value("Return the name of the machine")> _     Public ReadOnly Property MachineName( ) As String         Get             Return machine         End Get     End Property         <Value("Returns the IP address of the machine")> _     Public ReadOnly Property IPAddress( ) As IPAddress         Get             Return ip         End Get     End Property     End Class 

In comparison to gathering type information using reflection, retrieving information on custom attributes using reflection is quite easy. Only two methods are needed for retrieving custom attributes:

  • GetCustomAttribute

  • GetCustomAttributes

These methods are members of System.Type and of each of the Info classes (like ConstructorInfo , MethodInfo , and PropertyInfo ). In addition, they are shared methods of the Attribute class.

Much of the code from previous examples is already in place for navigating types in an assembly (it's somewhat lacking, but it will work here), so Example 7-3 can be modified to handle custom attributes. The real work will be ordering the attribute, which are not returned in any particular order. Unfortunately, ordering the attribute means that the arrays of returned custom attributes will have to be iterated several times to get the needed information. The custom attributes are extracted in the following order:

For classes
  • Summary

  • Remarks

  • History

For methods
  • Summary

  • Remarks

  • Param

For properties
  • Summary

  • Remarks

  • Value

Using the ObjectInfo class from Example 7-3, a method can be added to handle each custom attribute. The first is DumpSummary , which is shown here:

 Private Sub DumpSummary(ByVal o As Object)         Dim summary As SummaryAttribute         If TypeOf o Is Type Then         summary = Attribute.GetCustomAttribute( _             CType(o, Type), GetType(SummaryAttribute), False)     ElseIf TypeOf o Is MemberInfo Then         summary = Attribute.GetCustomAttribute( _             CType(o, MemberInfo), GetType(SummaryAttribute), False)     End If         If Not summary Is Nothing Then         Console.WriteLine("{0}{1}", vbTab, summary.Text)     End If     End Sub 

DumpSummary takes an Object parameter because it must be able to handle retrieval of attributes from a Type (a class) or some derivative of MemberInfo : ConstructorInfo , MethodInfo , or PropertyInfo .

Several overloaded GetCustomAttribute methods are available, depending on the program element to which the attribute is applied. The one that works best here allows the attribute to be retrieved by name. The first parameter to the call is the Type where the custom attribute is located. The second is the Type of the attribute itself. And the third is a Boolean flag that indicates whether or not the inheritance chain should be searched for the attribute. If the summary attribute exists, the SummaryAttribute.Text property can be written out to the console.

The Remarks and Value attributes are implemented in just about the same manner as Summary because each attribute can be applied only once per entity. Value is slightly different because it can be applied only to a property, so the method responsible for it will accept only a PropertyInfo object as a parameter. The code used to extract custom Remarks and Value attributes is:

 Private Sub DumpRemarks(ByVal o As Object)         Dim remarks As RemarksAttribute         If TypeOf o Is Type Then         remarks = Attribute.GetCustomAttribute( _             CType(o, Type), GetType(RemarksAttribute), False)     ElseIf TypeOf o Is MemberInfo Then         remarks = Attribute.GetCustomAttribute( _             CType(o, MemberInfo), GetType(RemarksAttribute), False)     End If         If Not remarks Is Nothing Then         Console.WriteLine("{0}{1}", vbTab, remarks.Text)     End If     End Sub     Private Sub DumpValue(ByVal p As PropertyInfo)         Dim value( ) As ValueAttribute = _         p.GetCustomAttributes(GetType(ValueAttribute), False)         If value.Length Then         Console.WriteLine("{0}{1}", vbTab, value(0).Text)     End If     End Sub 

The method responsible for Param attributes requires a little more work because attributes are not ordered in the assembly. However, the Param attributes can be matched on the parameter name using reflection, so this is not really a big problem. The code for the DumpParameters method is:

 Private Sub DumpParameters(ByVal m As MemberInfo, _                             ByVal params( ) As ParameterInfo)         Dim param As ParameterInfo     Dim attribute As ParamAttribute     Dim attributes( ) As ParamAttribute     attributes = m.GetCustomAttributes( _                     GetType(ParamAttribute), False)         If attributes.Length Then         Console.WriteLine(String.Format("{0}Parameters:", vbTab))     Else         Return     End If         For Each param In params         For Each attribute In attributes             If String.Compare(attribute.Name, param.Name) = 0 Then                 Console.WriteLine("{0}{1}{2}", _                     vbTab, vbTab, attribute.Name)                 Console.WriteLine("{0}{1}{2}", _                     vbTab, vbTab, attribute.Text)             End If         Next attribute     Next param     End Sub 

DumpParameters needs to handle constructors and methods, which is why the first parameter to the method is a MemberInfo object (the parent to both ConstructorInfo and MethodInfo ). The second parameter is an array of ParameterInfo objects that are retrieved before the method is called, by either DumpMethods or DumpConstructors .

This section concludes the discussion on reflection and attributes. Example 7-12 contains the final listing with all calls to the custom attribute retrieval methods in place. It is a monster of a listing! It also includes DumpHistory , which handles the history attribute from earlier in the chapter. When run, the output generated from the ServerInfo.dll assembly should look like this:

 --------------------------------------------------------------------------- Class:  ServerInfo2.ServerInfo         The Amazing ServerInfo Class(tm)         This class provides simple machine info.             History:          4/5/2002 JPH Came up with this a mere two months before                   publishing.          4/6/2002 JPH Tried to load assembly remotely but failed          4/7/2002 JPH No one seems to know anything about auto deploy.     Constructors: Public Sub New( )     Events: TaskCompleted     Properties: Public ReadOnly Property IPAddress( ) As System.Net.IPAddress         Returns the IP address of the machine Public ReadOnly Property MachineName( ) As System.String         Return the name of the machine     Methods: Public Sub DoTask(task As ServerInfo2.ServerInfo+Tasks)         Parameters:                 task                 The task you want to perform Public Shared Function GetMachineTime( )         Gets date and time on machine Public Function GetProcessorUsed( )         Calculates percentage of processor being used         This routine uses a sampling performance counter Public Function GetAvailableMemory( )         Returns available memory is megabytes 

This example is not a pretty formatting job, but that's life in the fast lane. If everything is done already, anyone could program a computer, right?

The ObjectInfo class is woefully incomplete, mainly due to lack of space. Look at all the code in this chapter for the partial implementation. The code doesn't do events or member data, so look at EventInfo and FieldInfo if curiosity takes hold. It also lacks support for reflecting inheritance or polymorphic attributes. Furthermore, it cannot determine what interfaces are implemented by a class.

All the classes involved in pursuing a full implementation were discussed. That's the good news. Here's some more good news: these classes, and especially System.Type , are incredibly full featured (and large).

Example 7-12. Final listing of ObjectInfo.vb
 'assumes serverinfo.dll is in the same directory     Imports Microsoft.VisualBasic Imports System Imports System.Reflection     Public Class ObjectInfo         Private Sub DumpConstructors(ByVal t As Type)             Dim c As ConstructorInfo         Dim ci( ) As ConstructorInfo = _             t.GetConstructors(BindingFlags.Public Or BindingFlags.Instance)             Console.WriteLine( )         Console.WriteLine("Constructors:")             For Each c In ci             Dim s As String = c.ToString( )             Console.WriteLine(s.Replace("Void .ctor", "Public Sub New"))             DumpSummary(c)             DumpRemarks(c)             DumpParameters(c, c.GetParameters( ))         Next c         End Sub         Private Sub DumpEvents(ByVal t As Type)         Dim e As EventInfo         Dim ei As EventInfo( ) = _                     t.GetEvents(BindingFlags.Public Or _                                 BindingFlags.Instance)             Console.WriteLine( )         Console.WriteLine("Events:")             For Each e In ei             Console.WriteLine(e.Name)         Next e         End Sub         Private Sub DumpProperties(ByVal t As Type)             Dim p As PropertyInfo         Dim pi( ) As PropertyInfo = _             t.GetProperties(BindingFlags.Public Or BindingFlags.Instance)             Console.WriteLine( )         Console.WriteLine("Properties:")             For Each p In pi             Console.Write("Public ")             If p.CanRead And p.CanWrite Then             ElseIf p.CanRead Then                 Console.Write("ReadOnly ")             ElseIf p.CanWrite Then                 Console.Write("WriteOnly ")             End If             Console.WriteLine("Property {0}( ) As {1}", _                 p.Name, p.PropertyType.ToString( ))             DumpSummary(p)             DumpRemarks(p)             DumpValue(p)         Next p         End Sub         Private Sub DumpMethod(ByVal m As MethodInfo)             Dim i As Integer         Dim returnType As Type         Dim returnString As String             If m.IsPublic Then             Console.Write("Public ")         ElseIf m.IsPrivate Then             Console.WriteLine("Private ")         ElseIf m.IsFamily Then             Console.WriteLine("Protected ")         ElseIf m.IsAssembly Then             Console.WriteLine("Friend ")         ElseIf m.IsFamilyAndAssembly Then             Console.WriteLine("Protected Friend ")         End If             If m.IsStatic Then             Console.Write("Shared ")         End If             returnType = m.ReturnType             If String.Compare(returnType.ToString( ), "System.Void") <> 0 Then             Console.Write("Function {0}(", m.Name)             returnString = String.Format(" As {0}", returnType.ToString( ))         Else             Console.Write("Sub {0}(", m.Name)         End If             Dim parms( ) As ParameterInfo = m.GetParameters( )         For i = 0 To parms.Length - 1             Console.Write(parms(i).Name)             Console.Write(" As ")             Console.Write(parms(i).ParameterType( ))                 If (i < parms.Length - 1) Then                 Console.Write(", ")             End If         Next i             Console.WriteLine(")")             DumpParameters(m, parms)         End Sub         Private Sub DumpMethods(ByVal t As Type)             Dim m As MethodInfo         Dim mi( ) As MethodInfo = t.GetMethods( )             Console.WriteLine( )         Console.WriteLine("Methods:")             For Each m In mi             If m.DeclaringType.Equals(t) AndAlso _                m.IsSpecialName = False Then                 DumpMethod(m)                 DumpSummary(m)                 DumpRemarks(m)             End If         Next m         End Sub         Private Sub DumpClassInfo(ByVal t As Type)             Console.WriteLine(New String("-"c, 80))         Console.WriteLine("Class: {0}{1}", vbTab, t.FullName)             DumpSummary(t)         DumpRemarks(t)         DumpConstructors(t)         DumpEvents(t)         DumpProperties(t)         DumpMethods(t)             Console.WriteLine( )         End Sub         Private Sub DumpSummary(ByVal o As Object)             Dim summary As SummaryAttribute             If TypeOf o Is Type Then             summary = Attribute.GetCustomAttribute( _                 CType(o, Type), GetType(SummaryAttribute), False)         ElseIf TypeOf o Is MemberInfo Then             summary = Attribute.GetCustomAttribute( _                 CType(o, MemberInfo), GetType(SummaryAttribute), False)         End If             If Not summary Is Nothing Then             Console.WriteLine("{0}{1}", vbTab, summary.Text)         End If         End Sub         Private Sub DumpRemarks(ByVal o As Object)             Dim remarks As RemarksAttribute             If TypeOf o Is Type Then             remarks = Attribute.GetCustomAttribute( _                 CType(o, Type), GetType(RemarksAttribute), False)         ElseIf TypeOf o Is MemberInfo Then             remarks = Attribute.GetCustomAttribute( _                 CType(o, MemberInfo), GetType(RemarksAttribute), False)         End If             If Not remarks Is Nothing Then             Console.WriteLine("{0}{1}", vbTab, remarks.Text)         End If         End Sub         Private Sub DumpValue(ByVal p As PropertyInfo)             Dim value( ) As ValueAttribute = _             p.GetCustomAttributes(GetType(ValueAttribute), False)             If value.Length Then             Console.WriteLine("{0}{1}", vbTab, value(0).Text)         End If         End Sub         Private Sub DumpParameters(ByVal m As MemberInfo, _                                ByVal params( ) As ParameterInfo)             Dim param As ParameterInfo         Dim attribute As ParamAttribute         Dim attributes( ) As ParamAttribute         attributes = m.GetCustomAttributes( _                         GetType(ParamAttribute), False)             If attributes.Length Then             Console.WriteLine(String.Format("{0}Parameters:", vbTab))         Else             Return         End If             For Each param In params             For Each attribute In attributes                 If String.Compare(attribute.Name, param.Name) = 0 Then                     Console.WriteLine("{0}{1}{2}", _                         vbTab, vbTab, attribute.Name)                     Console.WriteLine("{0}{1}{2}", _                         vbTab, vbTab, attribute.Text)                 End If             Next attribute         Next param         End Sub         Private Sub DumpHistory(ByVal t As Type)         If Not t.IsClass Then             Return         End If         Dim history As HistoryAttribute         Dim histories( ) As HistoryAttribute = _              Attribute.GetCustomAttributes _             (t, GetType(HistoryAttribute), False)             If histories.Length Then             Array.Sort(histories)             Console.WriteLine( )             Console.WriteLine("{0}History:", vbTab)             For Each history In histories                 Console.WriteLine("{0} {1} {2} {3}", _                     vbTab, _                     history.Date.ToShortDateString( ), _                     history.Author, _                     history.Change)             Next         End If     End Sub         Public Sub New(ByVal assemblyName As String)             Dim a As [Assembly]         If (assemblyName = Nothing) Then             a = [Assembly].GetExecutingAssembly( )         Else             a = [Assembly].LoadFrom(assemblyName)         End If             Dim t As Type         Dim types As Type( ) = a.GetTypes( )             For Each t In types             If t.IsClass Then                 DumpClassInfo(t)             End If         Next t         End Sub     End Class     Public Class Application     Public Shared Sub Main( )         Dim oi As New ObjectInfo("ServerInfo.dll")         Console.ReadLine( )     End Sub End Class 
only for RuBoard


Object-Oriented Programming with Visual Basic. Net
Object-Oriented Programming with Visual Basic .NET
ISBN: 0596001460
EAN: 2147483647
Year: 2001
Pages: 112
Authors: J.P. Hamilton

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