The Role of Reflection in VB (.NET)

   Posted by: Andrew Troelsen

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?.which we will look at in another post!

Understanding VB Anonymous Types

   Posted by: Andrew Troelsen

As an OO programmer, you know the benefits of defining classes to represent the state and functionality of a given programming entity. To be sure, whenever you need to define a class that is intended to be reused across projects and provides numerous bits of functionality through a set of methods, events, properties, and custom constructors, creating a new VB class is common practice and often mandatory.

However, there are other times in programming when you would like to define a class simply to model a set of encapsulated (and somehow related) data points without any associated methods, events, or other custom functionality. Furthermore, what if this type is only used internally to your current application and it?s not intended to be reused? If you need such a ?temporary? type, earlier versions of VB would require you to nevertheless build a new class definition by hand:

   1:  Class SomeClass
   2:    ' Define a set of private member variables...
   3:   
   4:    ' Make a property for each member variable...
   5:   
   6:    ' Override ToString() to account for each member variable...
   7:   
   8:    ' Override GetHashCode() and Equals() to work with value-based equality...
   9:  End Class

While building such a class is not rocket science, it can be rather labor intensive if you are attempting to encapsulate more than a handful of members. As of VB 2008, we are now provided with a massive shortcut for this very situation termed anonymous types. Consider the following VB sub:

   1:  Sub Main()
   2:    Console.WriteLine("***** Fun with Anonymous Types *****")
   3:   
   4:    ' Make an anonymous type representing a car.
   5:    Dim myCar = New With { .Color = "Bright Pink", _
   6:                           .Make = "Saab", .CurrentSpeed = 55 } 
   7:   
   8:    ' Now show the color and make.
   9:    Console.WriteLine("My car is a {0} {1}.", myCar.Color, myCar.Make)
  10:    Console.ReadLine()
  11:  End Sub

Again note that the myCar variable must be implicitly typed, which makes good sense, as we are not modeling the concept of an automobile using a strongly typed class definition. At compile time, the VB compiler will autogenerate a uniquely named class on our behalf. Given the fact that this class name is not directly visible from our VB code base, the use of implicit typing is in this case mandatory.

Also notice that we have to specify (using object initialization syntax) the set of properties that model the data we are attempting to encapsulate. Once defined, these values can then be obtained using standard VB property invocation syntax.

All anonymous types are automatically derived from System.Object, and therefore support each of the members provided by this base class on the implicitly typed myCar object. Assume your initial module defines the following new method:

   1:  Sub ReflectOverAnonymousType(ByVal obj As Object)
   2:    Console.WriteLine("obj is an instance of: {0}", obj.GetType().Name)
   3:    Console.WriteLine("Base class of {0} is {1}", _
   4:      obj.GetType().Name, _
   5:      obj.GetType().BaseType)
   6:    Console.WriteLine("obj.ToString() = {0}", obj.ToString())
   7:    Console.WriteLine("obj.GetHashCode() = {0}", obj.GetHashCode())
   8:    Console.WriteLine()
   9:  End Sub

Now assume we invoke this method from Main(), passing in the myCar object as the parameter:

   1:  Sub Main()
   2:    Console.WriteLine("***** Fun with Anonymous Types *****")
   3:   
   4:    ' Make an anonymous type representing a car.
   5:    Dim myCar = New With {.Color = "Bright Pink", .Make = "Saab", .CurrentSpeed = 55}
   6:   
   7:    ' Now show the color and make.
   8:    Console.WriteLine("My car is a {0} {1}.", myCar.Color, myCar.Make)
   9:   
  10:    ' Reflect over what the compiler generated.
  11:    ReflectOverAnonymousType(myCar)
  12:    Console.ReadLine()
  13:  End Sub

Check out the output shown here:

clip_image002

First of all, notice that in this example, the myCar object is of type VB$AnonymousType_0`3 (your name may differ). Remember that the assigned type name is completely determined by the compiler and is not directly accessible in your VB code base.

Perhaps most important, notice that each name/value pair defined using the object initialization syntax is mapped to an identically named property (which can be verified using tools such as reflector.exe or ildasm.exe). Because a VB anonymous type yields read/write properties for each item defined within the initialization list, you are free to change the state of your object after creation using standard property syntax:

   1:  ' Make an anonymous type representing a car.
   2:  Dim myCar = New With {.Color = "Bright Pink", .Make = "Saab", .CurrentSpeed = 55}
   3:   
   4:  ' Now change the color.
   5:  myCar.Color = "Black"

It is possible to create an anonymous type that is composed of additional anonymous types. For example, assume you wish to model a purchase order that consists of a timestamp, a price point, and the automobile purchased. Here is a new (slightly more sophisticated) anonymous type representing such an entity:

   1:  Sub CompositeAnonymousType()
   2:    ' Make an anonymous type that is composed of another.
   3:    Dim purchaseItem = New With { _
   4:      .TimeBought = DateTime.Now, _
   5:      .ItemBought = New With {.Color = "Red", .Make = "Saab", .CurrentSpeed = 55}, _
   6:      .Price = 34.0}
   7:    ReflectOverAnonymousType(purchaseItem)
   8:  End Sub

At this point, you should understand the syntax used to define anonymous types, but you may still be wondering exactly where (and when) to make use of this new language feature. To be blunt, anonymous type declarations should be used sparingly, typically only when making use of the LINQ technology set. You would never want to abandon the use of strongly typed classes/structures simply for the sake of doing so, given anonymous types? numerous limitations, which include the following:

  • You don?t control the name of the anonymous type.
  • Anonymous types always extend System.Object.
  • Anonymous types cannot support events, custom methods, custom operators, or custom overrides.
  • Anonymous types are always implicitly sealed.
  • Anonymous types are always created using the default constructor.

However, when programming with the LINQ technology set, you will find that in many cases this syntax can be very helpful when you wish to quickly model the overall shape of an entity rather than its functionality.

VB Object Initialization Syntax

   Posted by: Andrew Troelsen

VB 2008 introduced a new way to hydrate the state of a new class or structure variable termed object initializer syntax. Using this technique, it is possible to create a new type variable and assign a slew of properties and/or public fields in a few lines of code. Syntactically, an object initializer consists of a comma-delimited list of specified values, enclosed by the { and } tokens, preceded with the With keyword. Each member in the initialization list maps to the name of a public field or public property of the object being initialized.

Now, consider the following Point type:

   1:  Public Class Point
   2:    Private xPos As Integer, yPos As Integer
   3:   
   4:    Public Sub New(ByVal x As Integer, ByVal y As Integer)
   5:      xPos = x
   6:      yPos = y
   7:    End Sub
   8:    Public Sub New()
   9:    End Sub
  10:   
  11:    Public Property X() As Integer
  12:      Get
  13:        Return xPos
  14:      End Get
  15:      Set (ByVal value As Integer)
  16:        xPos = value
  17:      End Set
  18:    End Property
  19:    Public Property Y() As Integer
  20:      Get
  21:        Return yPos
  22:      End Get
  23:      Set (ByVal value As Integer)
  24:        yPos = value
  25:      End Set
  26:    End Property
  27:   
  28:    Public Overrides Function ToString() As String
  29:      Return String.Format("[{0}, {1}]", xPos, yPos)
  30:    End Function
  31:  End Class

We could now make Points using any of the following approaches:

   1:  Sub Main()
   2:    Console.WriteLine("***** Fun with Object Init Syntax *****") 
   3:    ' Make a Point by setting each property manually...
   4:    Dim firstPoint As New Point() 
   5:    firstPoint.X = 10 
   6:    firstPoint.Y = 10 
   7:    Console.WriteLine(firstPoint.ToString())
   8:   
   9:    ' ...or make a Point via a custom constructor...
  10:    Dim anotherPoint As New Point(20, 20) 
  11:    Console.WriteLine(anotherPoint.ToString())
  12:   
  13:    ' ...or make a Point types using the new object init syntax.
  14:    Dim finalPoint As New Point With { .X = 30, .Y = 30 } 
  15:    Console.WriteLine(finalPoint.ToString())
  16:   
  17:    Console.ReadLine() 
  18:  End Sub

The final Point variable is not making use of a custom type constructor (as one might do traditionally), but are rather setting values to the public X and Y properties. Behind the scenes, the type?s default constructor is invoked, followed by setting the values to the specified properties. To this end, finalPoint is just shorthand notations for the syntax used to create the firstPoint variable (going property by property).

Now recall that this same syntax can be used to set public fields of a type, which Point currently does not support. However, for the sake of argument, assume that the xPos and yPos member variables have been declared publicly. We could now set values to these fields as follows:

   1:  Dim p As New Point With {.xPos = 2, .yPos = 3} 

Given that Point now has four public members, the following syntax is also legal. However, try to figure out the actual final values of xPos and yPos:

   1:  Dim p As New Point With {.xPos = 2, .yPos = 3, .X = 900} 

As you might guess, xPos is set to 900, while yPos is the value 3. From this, you can correctly infer that object initialization is performed in a left-to-right manner. To clarify, the previous initialization of p using standard object constructor syntax would appear as follows:

   1:  Dim p As New Point() 
   2:  p.xPos = 2 
   3:  p.yPos = 3 
   4:  p.X = 900 
Now assume we now have a Rectangle class, which makes use of the Point type to represent its upper-left/bottom-right coordinates:
   1:  Public Class Rectangle
   2:    Private m_topLeft As New Point()
   3:    Private m_bottomRight As New Point()
   4:   
   5:    Public Property TopLeft() As Point
   6:      Get
   7:        Return m_topLeft
   8:      End Get
   9:      Set (ByVal value As Point)
  10:        m_topLeft = value
  11:      End Set
  12:    End Property
  13:    Public Property BottomRight() As Point
  14:      Get
  15:        Return m_bottomRight
  16:      End Get
  17:      Set (ByVal value As Point)
  18:        m_bottomRight = value
  19:      End Set
  20:    End Property
  21:   
  22:    Public Overrides Function ToString() As String
  23:      Return String.Format("[TopLeft: {0}, {1}, BottomRight: {2}, {3}]", _
  24:      m_topLeft.X, m_topLeft.Y, m_bottomRight.X, m_bottomRight.Y)
  25:    End Function
  26:  End Class

Using object initialization syntax, we could create a new Rectangle type and set the inner Points as follows:

   1:  ' Create and initialize a Rectangle.
   2:  Dim myRect As New Rectangle() With 
   3:  { 
   4:    .TopLeft = New Point() With { .X = 10, .Y = 10 }, 
   5:    .BottomRight = New Point() With { .X = 200, .Y = 200} 
   6:  }

Although this VB syntax has been supported since .NET 3.5, it can be easy to forget about this streamlined way to hydrate objects and fallback on older techniques.

Again, the benefit of this new syntax is that it basically decreases the number of keystrokes (assuming there is not a suitable constructor). Here is the traditional approach to establishing a similar Rectangle:

   1:  ' Old-school approach.
   2:  Dim r As New Rectangle() 
   3:  Dim p1 As New Point() 
   4:  p1.X = 10 
   5:  p1.Y = 10 
   6:  r.TopLeft = p1 
   7:  Dim p2 As New Point() 
   8:  p2.X = 200 
   9:  p2.Y = 200 
  10:  r.BottomRight = p2 

Happy coding!

Find Us
Contact Us 651-288-7000 1-800-866-9884
Home | Training | Curriculum | Course Finder | Schedule | Enroll | Twin Cities Java User Group | Consulting | Foundation | Jobs | About Us | Our Story | Press Room | Instructors | President | Map & Directions | Sitemap

Java Training | JSF / Struts / Spring / Hibernate Training | Java Power Tools Training | .NET 4.0 & Visual Studio 2010 Training | Microsoft Web Development Training | Prism / MVVM / MEF Training | .NET 3.5 and Visual Studio 2008 Training | .NET 2.0 and Visual Studio 2003 Training | Cloud Computing Training | Ajax / Web Services / XML Training | Groovy and Grails Training | SQL Server 2012 Training | SQL Server 2008 Training | SQL Server 2005 Training | Mobile Development Training | SharePoint 2010 Training | SharePoint 2007 Training | Agile, Process, Analysis & Design Training | Arch/Design Patterns Training | Microsoft Official Curriculum Training | Web Development Training | Ruby Training | Rational Application Developer (RAD) Training | WebSphere Application Server Training | WebSphere Portal Training | WebLogic Training | Boot Camp Training | Project Management Training | C / C++ Training | Metro / WinRT / Windows 8 Development Training | Retired

Intertech delivers training on-site and virtually serving cities including Phoenix, AZ | San Francisco, CA | Los Angeles, CA | San Diego, CA | San Jose, CA | Washington, DC | Chicago, IL | Orlando, FL | Boston, MA | Duluth, MN | Minneapolis St. Paul, MN | Rochester, MN | Raleigh-Durham, NC | New York, NY | Philadelphia, PA | Austin, TX | Dallas, TX | Houston, TX | Seattle, WA.