7.2 Runtime Type Discovery

only for RuBoard

Example 7-1 contains a listing for a class called ServerInfo . Consider it the humble beginnings of a load balancer. It can provide the machine name , an IP address, the processor usage, and the available memory of the machine on which it runs. However, in this chapter, it serves more form than function, so for now, forget about what it does and look at what it contains: an event, an enumeration, three methods (a sub, a function, and a shared function), and two read-only properties. While pondering these contents deeply, save it to a file named ServerInfo.vb and compile it to a class library. You will need this assembly as the basis of the rest of the chapter.

Example 7-1. Reflection test class
 'vbc /t:library serverinfo.vb /r:system.dll     Imports System Imports System.Diagnostics Imports System.Net Imports System.Threading     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 As EventHandler         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     Public Sub DoTask(ByVal task As Tasks)         RaiseEvent TaskCompleted(Me, EventArgs.Empty)     End Sub         'Shared method     Public Shared Function GetMachineTime( ) As DateTime         Return DateTime.Now     End Function         'Get % of process currently in use     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         'Get MBytes of free memory     Public Function GetAvailableMemory( ) As Long         If PerformanceCounterCategory.Exists("Memory") Then             Dim pc As New PerformanceCounter("Memory", "Available MBytes")             Return pc.RawValue( )         End If     End Function         Public ReadOnly Property MachineName( ) As String         Get             Return machine         End Get     End Property         Public ReadOnly Property IPAddress( ) As IPAddress         Get             Return ip         End Get     End Property     End Class 

The listing for ServerInfo.dll is in plain sight, so seeing its contents is easy. However, pretend that the source code doesn't exist. Can the assembly be queried programmatically for its type information? Sure it can. That's what reflection is all about.

The System.Reflection namespace and the System.Type class together will meet most reflection needs. This chapter will not discuss everything that can be accomplished with these classes. By the end of the chapter, though, your handle on reflection should be tighter than a G.I. Joe with Kung-Fu Action Grip.

The first step in type discovery is loading the assembly (the executable or library) in question; Example 7-2 shows the code that does this. Note that " Assembly " is a reserved word, so it is enclosed in square brackets in Example 7-2. If this were not done, System.Reflection.Assembly would have to be used instead. Who wants to type all that? The example also assumes that ServerInfo.dll is located in the same directory where the compiled executable will reside. If it is not, you will have to provide a full path to the Assembly.LoadFrom method.

Example 7-2. Runtime type inspection
 Imports System Imports System.Reflection     Public Class ObjectInfo          Public Sub New (ByVal assemblyName As String)  Dim a As [Assembly] = [Assembly].LoadFrom(assemblyName)  End Sub     End Class     Public Class Application     Public Shared Sub Main( )         Dim oi As New ObjectInfo("ServerInfo.dll")         Console.ReadLine( )     End Sub End Class 

Save this example to a file called reflect.vb . Or, as an alternative, add the Application class and the ObjectInfo class to the ServerInfo.dll assembly and recompile it as an executable. In that case, the constructor to ObjectInfo needs modification so it obtains a reference to the current assembly instead of ServerInfo.dll . Modify it by replacing the call to Assembly.LoadFrom with Assembly.GetExecutingAssembly :

 'Get the currently executing assembly Dim a As [Assembly] = [Assembly].GetExecutingAssembly( ) 

At this point, the ObjectInfo class does not do much more than load an assembly. Before compiling, remedy the situation by grabbing all the types in ServerInfo.dll and writing them out to the console. Doing so will get the ball rolling. The code is as follows :

 Public Class ObjectInfo          Public Sub New (ByVal assemblyName As String)         Dim a As [Assembly] = [Assembly].LoadFrom(assemblyName)  Dim t As Type   Dim types As Type( ) = a.GetTypes( )   For Each t In  types   Console.WriteLine(t.FullName)   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 

Assembly.GetTypes returns an array of System.Type . This array represents every type in the assembly. The types contained in ServerInfo.dll will come back as follows:

 ServerInfo.ServerInfo ServerInfo.ServerInfo+Tasks 

There are two types in the assembly: the ServerInfo class and an enumeration named Tasks . To avoid confusion, properties, methods, and constructors are not categorized as types. They are considered elements of types.

Theoretically, the ServerInfo.dll assembly could contain more classes. As the types contained in the assembly are iterated in the ObjectInfo class, it might be beneficial to know what is what. System.Type contains several Boolean properties, shown in Table 7-1, that can help determine what a Type object represents.

Table 7-1. Type classification properties in System.Type

Method

Description

IsArray

Type is an array.

IsClass

Type is a class.

IsCOMObject

Type is a COM object.

IsEnum

Type is an enum.

IsInterface

Type is an interface.

IsPointer

Type is a pointer.

IsPrimitive

Type is a primitive data type.

IsValueType

Type is a structure.

It's a good idea to modify the constructor in ObjectInfo to look for class types only, since for the sake of simplicity, this chapter will discuss only classes. If a class is found, we'll pass the type along to a private method named DumpClassInfo (the source code for which is presented later in this chapter):

 For Each t In  types     If t.IsClass( ) Then         DumpClassInfo(t)         End If Next t 

Try to follow the discussion for now. Don't worry about coding along. After everything is discussed, a final code listing will contain everything.

The private method, DumpClassInfo , relies on several methods provided by the System.Type class that return Info objects. This is a general name given to the .NET class library classes of the following types: ConstructorInfo , EventInfo , FieldInfo , MethodInfo , ParameterInfo , and PropertyInfo . Each class represents the attributes of a class entity, and each is derived from a common parent, MemberInfo .

You can obtain references for these objects by calling a corresponding Get method on System.Type . For instance, Type.GetConstructors returns an array of ConstructorInfo objects, and Type.GetMethods returns an array of MethodInfo objects. These classes provide information about the constructors and methods of a class, respectively. Each Get method also has a single form: Type.GetConstructor , Type.GetProperty , and so forth. Table 7-2 summarizes these methods.

Table 7-2. Info methods from System.Type

Method

Returns

GetConstructor/GetConstructors

ConstructorInfo

GetEvent/GetEvents

EventInfo

GetField/GetFields

FieldInfo

GetMethod/GetMethods

MethodInfo

GetParameter/GetParameters

ParameterInfo

GetProperty/GetProperties

PropertyInfo

Using the specific classes in Table 7-2 is not necessary, but they do make life easier. Type.GetMembers returns an array of MemberInfo objects. From here, you can determine the specific type of each member by examining the MemberInfo object that corresponds to it. Here's a quick example. Given that t is an instance of Type (that represents a class), the following code is possible:

 Dim member As MemberInfo Dim members( ) As MemberInfo = t.GetMembers( )     For Each member In members         Select Case member.MemberType         Case MemberTypes.Constructor                  Case MemberTypes.Event                  Case MemberTypes.Field                  Case MemberTypes.Method                  Case MemberTypes.Property     End Select     Next member 

However, using the specific Info methods provided through Type is easier.

The time to write ObjectInfo.DumpClassInfo draws nigh. This method, once written, can be used to examine any class. The final listing will be fairly long, so this chapter will examine the code one step at a time. All pretenses aside, the primary motivation of this example is to cover as much of reflection as possible without turning the chapter into a reference manual.

Regardless, the example needs some semblance of credibility. To give it a purpose, let's approach it as if it will be used to generate class documentation. This is a practical for reflection.

DumpClassInfo should first output the name of the class by calling Type.FullName . This step returns the fully qualified name of the type (meaning it includes the namespace):

 Private Sub DumpClassInfo(ByVal t As Type)     Console.WriteLine(New String("-"c, 80))     Console.WriteLine("Class: {0}", t.FullName) 

7.2.1 Constructor Information

It's easy to get the constructors by calling Type.GetConstructors . This method returns an array of ConstructorInfo . One of the overrides of GetConstructors accepts a bitmask containing values from the BindingFlags enumeration as a parameter. This enumeration provides a means to restrict what is searched during reflection. Here, the Public and Instance members of the BindingFlags enumeration are used, so only the public instance constructors are asked for. Any nonpublic constructors will not be returned:

 'Get constructors  Dim c As ConstructorInfo   Dim ci( ) As ConstructorInfo = _   t.GetConstructors(BindingFlags.Public Or BindingFlags.Instance)  Console.WriteLine("Constructors:")              For Each c In ci         Console.WriteLine("    {0}", c)     Next c 

If BindingFlags.Instance (or BindingFlags.Static ) is not used in conjunction with BindingFlags.Public (or BindingFlags.NonPublic ), no members will be returned.

Examining the output of the previous block of code, note that the enumeration is no longer listed. It is not listed because the call to Type.IsClass wraps the call to DumpClassInfo . The output, thus far, should look similar to this:

 --------------------------------------------------------------------------- Class: ServerInfo.ServerInfo Constructors:     Void .ctor( ) 

Now, this example is definitely not Visual Basic. In C, C++, C#, and Java, void denotes a function that does not return a value. In VB.NET, a function that does not return a value is a Sub . To make this code readable to VB.NET programmers, you only need to replace Void with Public Sub . The .ctor is short for "constructor," or in VB.NET-speak, New .

Translating the output into VB.NET is a fairly straightforward string replacement. Instead of writing the raw constructor signature to the console, use this code:

 For Each c In ci     Dim s As String = String.Format("{0}{1}", vbTab, c.ToString( ))     Console.WriteLine(s.Replace("Void .ctor", "Public Sub New")) Next c 

Even if the BindingFlags enumeration were not used as a filter in this example, the ConstructorInfo class (like all the Info classes) contains all the members needed to determine everything about a class' attributes: Is it public? Is it private? Is it abstract? Is inheritance allowed? And so on.

This chapter does not cover the minutiae of it all. Just understand that all the classes that derive from MemberInfo are very similar in form and function.

7.2.2 Property Information

The code for obtaining public instance properties is similar to that for constructors. Additionally, a call to PropertyInfo.CanRead and PropertyInfo.CanWrite can determine the property's accessibility. The following code, when added to the DumpClassInfo method, displays property information:

 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( )) Next p 

This code returns the properties as VB.NET declarations:

 --------------------------------------------------------------------------- Class: ServerInfo.ServerInfo Constructors:         Public Sub New( )  Properties  :  Public ReadOnly Property MachineName( ) As System.String   Public ReadOnly Property IPAddress( ) As System.Net.IPAddress  

Apparently, the PropertyInfo class does not return as much information about a property as some of the other Info classes do for their counterparts. For instance, if you look at the class definition, there does not appear to be a way to do much beyond determining whether a property is read-only, write-only, or both (using the CanWrite and CanRead properties).

However, this impression is not completely accurate. Behind the scenes, VB.NET properties are implemented with corresponding Get and Set methods (see Section 3.2 in Chapter 3). To get additional information about the property (such as whether it is private, inheritable, or shared), you must work with these methods rather than with the PropertyInfo class. The next section details the type of information you can obtain by examining a method. First, though, you need to get to the accessor methods.

You can access the accessor methods in one of two ways: by calling either PropertyInfo.GetGetMethod or PropertyInfo.GetSetMethod . Each returns a MethodInfo class that represents the appropriate accessor. Also, it is possible to call PropertyInfo.GetAccessors , which returns an array of MethodInfo objects. This class is very similar to ConstructorInfo and contains all the functionality necessary to describe the method in question.

7.2.3 Method Information

To get the methods of a class, call Type.GetMethods , which returns an array of MethodInfo objects. This time, however, in the name of pure, unadulterated entertainment, the BindingFlags bitmask will not be used. Our goal is to demonstrate how the information provided by BindingFlags can be determined without using the bitmask.

While iterating through each method, you can see if the method is declared in the current class by calling MethodInfo.DeclaringType . Calling it filters out all methods that are inherited from System.Object or from another base class, had we used inheritance explicitly when creating the ServerInfo class. This way, the listing reflects the only current type, the ServerInfo class, and its unique methods and events. The following code accomplishes this:

 'Get public shared methods Dim i As Integer Dim returnType As Type Dim returnString As String Dim m As MethodInfo Dim mi( ) As MethodInfo = t.GetMethods( )     Console.WriteLine("Methods:")     For Each m In mi         If m.DeclaringType.Equals(t) Then 

You can filter out inherited members in the call to GetMethods by supplying the BindingFlags.DeclaredOnly constant using a method call like the following:

 mi = t.GetMethods(BindingFlags.Public Or _                   BindingFlags.Instance Or _                   BindingFlags.DeclaredOnly) 

To determine the accessibility of a method, various properties are available from MethodInfo . See how these property names map IL to VB.NET:

 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 

These properties, such as IsPublic , IsFamily , and IsStatic , are defined in a class named MethodBase , which is the parent class of both MethodInfo and ConstructorInfo . ConstructorInfo functions the same way as MethodInfo , except it is focused on constructors.

To cut the example's size , polymorphic attributes like MustOverride , Overridable , and Overrides were left out. However, MethodInfo allows you to ascertain this information.

The return type for the reflected method can be snagged by calling, oddly enough, the property named ReturnType . Who could have guessed? If the return type is equal to System.Void , the method is a Sub ; otherwise , it's a Function . After determining which is which, our code calls MethodInfo.Name to retrieve the name of the method.

 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 

Divining the parameters for the method is as simple as calling MethodInfo.GetParameters , which returns an array of ParameterInfo objects. This class can query the parameter's name and type, as shown in the next code fragment for DumpClassInfo :

 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.Write(")")         Console.WriteLine(returnString)         End If     Next m 

Up to this point, the output of ServerInfo looks like this (if you reformatted it to fit in a book):

 Methods: Public Sub remove_TaskCompleted(obj As     ServerInfo.ServerInfo+TaskCompletedEventHandler) Public Sub add_TaskCompleted(obj As     ServerInfo.ServerInfo+TaskCompletedEventHandler) Public Sub DoTask(task As ServerInfo.ServerInfo+Tasks) Public Shared Function GetMachineTime( ) As System.DateTime Public Function GetProcessorUsed( ) As System.Single Public Function GetAvailableMemory( ) As System.Int64 Public Function get_MachineName( ) As System.String Public Function get_IPAddress( ) As System.Net.IPAddress 

get_MachineName and get_IPAddress are the read-only accessor methods of the MachineName and IPAddress properties, so they should probably not be listed here because they were already handled in the properties section. An additional property of the MethodInfo class called IsSpecialName can prevent the display of accessor methods. To use it, modify the If statement that tests to eliminate inherited members as follows:

 If m.DeclaringType.Equals(t)  AndAlso m.IsSpecialName = False Then  

If you disassemble ServerInfo.dll , you will see that both accessor methods have the specialname IL attribute associated with them. This flag is provided for compiler writers and tool vendors and allows a member to be treated specially. For instance, IntelliSense uses it to prevent the display of accessor methods. However, it doesn't do anything that affects the way code is run.

Once we eliminate methods with special names, the output appears as follows:

 Methods: Public Sub DoTask(task As ServerInfo.ServerInfo+Tasks) Public Shared Function GetMachineTime( ) As System.DateTime Public Function GetProcessorUsed( ) As System.Single Public Function GetAvailableMemory( ) As System.Int64 

7.2.4 The Complete ObjectInfo Source Code

Example 7-3 contains a revised listing of the ObjectInfo class. It was rewritten slightly, but all the functionality is there (including events). Your assignment, should you choose to accept it, is to add support for other types, such as enumerations and value types. Also, nested types (that is, classes contained within classes) are not handled at all. To handle them, look at Type.GetNestedTypes , if possible. There is really nothing to it. Everything in reflection is aptly named. You can learn a lot just by hacking the example.

Example 7-3 is long, but don't run away. The wonderful world of reflection is far from fully explored. More needs to be covered before the topic can be put to rest. But check out the example and spend some quality time with it.

Example 7-3. ObjectInfo reflection class
 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"))         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( ))         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(")")         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)             End If         Next m         End Sub         Private Sub DumpClassInfo(ByVal t As Type)             Console.WriteLine(New String("-"c, 80))         Console.WriteLine("Class: {0}", t.FullName)             DumpConstructors(t)         DumpEvents(t)         DumpProperties(t)         DumpMethods(t)             Console.WriteLine( )         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 
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