The Role of Reflection in VB (.NET)

The ability to fully describe types (classes, interfaces, structures, enumerations, and delegates) using metadata is a key element of the .NET platform. Numerous .NET technologies, such as object serialization, Windows Communication Foundation (WCF), and XML web services, require the ability to discover the format of types at runtime. Furthermore, COM interoperability, compiler support, and an IDE’s IntelliSense capabilities all rely on a concrete description of type.

The reflection API of the .NET platform allows you to dynamically discover the composition of a type at runtime. For example, using reflection, you could figure out at runtime which interfaces a class (or structure) implements, which events the object could fire your direction, the list of all properties, and so forth.

Programmatically speaking, the System.Type class represents ‘somebody’s metadata’. We can obtain this sort of type information using one of three techniques. Regarding your first choice, recall that System.Object defines a method named GetType(), which returns an instance of the Type class that represents the metadata for the current object:

       1:  ' Obtain type information using a SportsCar instance.

       2:  Dim sc As SportsCar = New SportsCar()

       3:  Dim t As Type = sc.GetType()

Obviously, this approach will only work if you have compile-time knowledge of the type you wish to investigate (SportsCar in this case). Given this restriction, it should make sense that tools such as ildasm.exe do not obtain type information by directly calling a custom type’s GetType() method, given that ildasm.exe was not compiled against your custom assemblies!

To obtain type information in a more flexible manner, you may call the shared GetType() member of the System.Type class and specify the fully qualified string name of the type you are interested in examining. Using this approach, you do not need to have compile-time knowledge of the type you are extracting metadata from, given that Type.GetType() takes an instance of the omnipresent System.String.

The Type.GetType() method has been overloaded to allow you to specify two Boolean parameters, one of which controls whether an exception should be thrown if the type cannot be found, and the other of which establishes the case sensitivity of the string. To illustrate, ponder the following:

       1:  ' Obtain type information using the shared Type.GetType() method

       2:  ' (don't throw an exception if SportsCar cannot be found and ignore case).

       3:  Dim t As Type = Type.GetType("CarLibrary.SportsCar", False, True)

The final way to obtain type information is using the VB GetType operator. Like Type.GetType(), the GetType operator is helpful in that you do not need to first create an object instance to extract type information. However, your code base must still have compile-time knowledge of the type you are interested in examining:

       1:  ' Get the Type using GetType.

       2:  Dim t As Type = GetType(SportsCar)

To illustrate the basic process of reflection (and the usefulness of System.Type), let’s create a Console Application named MyTypeViewer. Once you have done so, be sure to import the System.Reflection namespace into your initial code file (which I have renamed from Module1.vb to Program.vb). This program will display details of the methods, properties, fields, and supported interfaces (in addition to some other points of interest) for any type within mscorlib.dll (recall all .NET applications have automatic access to this core framework class library) or a type within MyTypeViewer.exe itself.

The Program module will be updated to define a number of subroutines, each of which takes a single System.Type parameter. First you have ListMethods(), which (as you might guess) prints the name of each method defined by the incoming type. Notice how the GetMethods() method returns an array of System.Reflection.MethodInfo types:

       1:  ' Display method names of type.

       2:  Public Sub ListMethods(ByVal t As Type)

       3:    Console.WriteLine("***** Methods *****")

       4:    Dim mi As MethodInfo() = t.GetMethods()

       5:    For Each m As MethodInfo In mi

       6:     Console.WriteLine("->{0}", m.Name)

       7:    Next

       8:    Console.WriteLine()

       9:  End Sub

The implementation of ListFields() is similar. The only notable difference is the call to the GetFields() method and the resulting FieldInfo array. Again, to keep things simple, you are printing out only the name of each field.

       1:  ' Display field names of type.

       2:  Public Sub ListFields(ByVal t As Type)

       3:    Console.WriteLine("***** Fields *****")

       4:    Dim fi As FieldInfo() = t.GetFields()

       5:    For Each field As FieldInfo In fi

       6:      Console.WriteLine("->{0}", field.Name)

       7:    Next

       8:    Console.WriteLine()

       9:  End Sub

The logic to display a type’s properties is similar:

       1:  ' Display property names of type.

       2:  Public Sub ListProps(ByVal t As Type)

       3:    Console.WriteLine("***** Properties *****")

       4:    Dim pi As PropertyInfo() = t.GetProperties()

       5:    For Each prop As PropertyInfo In pi

       6:      Console.WriteLine("->{0}", prop.Name)

       7:    Next

       8:    Console.WriteLine()

       9:  End Sub

Next, you will author a method named ListInterfaces() that will print out the names of any interfaces supported on the incoming type. The only point of interest here is that the call to GetInterfaces() returns an array of System.Types! This should make sense given that interfaces are, indeed, types:

       1:  ' Display implemented interfaces.

       2:  Public Sub ListInterfaces(ByVal t As Type)

       3:    Console.WriteLine("***** Interfaces *****")

       4:    Dim ifaces As Type() = t.GetInterfaces()

       5:    For Each i As Type In ifaces

       6:      Console.WriteLine("->{0}", i.Name)

       7:    Next

       8:    Console.WriteLine()

       9:  End Sub

Last but not least, you have one final helper method that will simply display various statistics (indicating whether the type is generic, what the base class is, whether the type is sealed, and so forth) regarding the incoming type:

       1:  ' Just for good measure.

       2:  Public Sub ListVariousStats(ByVal t As Type)

       3:    Console.WriteLine("***** Various Statistics *****")

       4:    Console.WriteLine("Base class is: {0}", t.BaseType)

       5:    Console.WriteLine("Is type abstract? {0}", t.IsAbstract)

       6:    Console.WriteLine("Is type sealed? {0}", t.IsSealed)

       7:    Console.WriteLine("Is type generic? {0}", t.IsGenericTypeDefinition)

       8:    Console.WriteLine("Is type a class type? {0}", t.IsClass)

       9:    Console.WriteLine()

      10:  End Sub

The Main() method of the Program module prompts the user for the fully qualified name of a type. Once you obtain this string data, you pass it into the Type.GetType() method and send the extracted System.Type into each of your helper methods. This process repeats until the user enters Q to terminate the application:

       1:  ' Need to make use of the reflection namespace.

       2:  Imports System.Reflection

       3:   

       4:  Module Program

       5:    Sub Main()

       6:      Console.WriteLine("***** Welcome to MyTypeViewer *****")

       7:      Dim typeName As String = String.Empty

       8:   

       9:      Do

      10:        Console.WriteLine()

      11:        Console.WriteLine("Enter a type name to evaluate")

      12:        Console.Write("or enter Q to quit: ")

      13:   

      14:        ' Get name of type.

      15:        typeName = Console.ReadLine()

      16:   

      17:        ' Does user want to quit?

      18:        If typeName.ToUpper() = "Q" Then

      19:          Exit Do

      20:        End If

      21:   

      22:        ' Try to display type

      23:        Try

      24:          Dim t As Type = Type.GetType(typeName)

      25:          Console.WriteLine()

      26:          ListVariousStats(t)

      27:          ListFields(t)

      28:          ListProps(t)

      29:          ListMethods(t)

      30:          ListInterfaces(t)

      31:        Catch

      32:          Console.WriteLine("Sorry, can't find {0}.", typeName)

      33:        End Try

      34:      Loop  

      35:    End Sub

      36:    ' Assume all the helper methods are defined below.

      37:  ...

      38:  End Module

At this point, MyTypeViewer.exe is ready to take out for a test drive. For example, run your application and enter the following fully qualified names (be aware that the manner in which you invoked Type.GetType() requires case-sensitive names):

  • System.Int32
  • System.Collections.ArrayList
  • System.Threading.Thread
  • System.Void
  • System.IO.BinaryWriter
  • System.Math
  • System.Console
  • MyTypeViewer.Program

Interesting stuff, huh? Clearly the System.Reflection namespace and System.Type class allow you to reflect over many other aspects of a type beyond what MyTypeViewer is currently displaying. For example, you can obtain a type’s events, get the list of any generic parameters for a given member and optional arguments, and glean dozens of other details.

Nevertheless, at this point you have created an (somewhat capable) object browser. The major limitation, of course, is that you have no way to reflect beyond the current assembly (MyTypeViewer.exe) or the always accessible mscorlib.dll. This begs the question, ‘How can I build applications that can load (and reflect over) external assemblies?’

Good question! We will look at in another post!

{Offer-Title}

{Offer-PageContent}
Click Here