Casting Reference Types in Visual Basic .NET

asting, or converting the value of a token from one type of data to another is common to most (if not all) programming languages. Visual Basic has long had a series of casting functions, such as CStr, CInt, CLong, etc. to allow conversion of one type of variable to another. While the idea of casting is simple and familiar when discussed in the context of a simple variable containing a value (.NET value types), the meaning and behavior of a cast is less clear when discussed in the context of objects.

Casting is the term used to describe the conversion of a value from one data type to another. In principle this is fairly straightforward to most experienced programmers. A String of “12” can be converted to an Integer of 12. A Date value of 12/07/41 can be converted to a String of “12/07/41” or even “December 7, 1941”.

Variables in .NET languages are referred to as “types”, and there are two types that you need to concern yourself with in any .NET language: value types and reference types. Value types are the simpler, or at least the more familiar of the two. Value types include Boolean and numeric values (Float, Long, Double, Integer), Date, and DateTime values. Value types are all based on structures rather than classes. Structures are stored on the memory stack rather than the heap and cannot be subclassed. Integer types are based on the System.Int32 structure, Long types are based on System.Int64, Boolean types are based on System.Boolean, DateTime types are based on System.DateTime, and so forth. The base value type structures derive from System.ValueType, which is in turn derived from System.Object.

Figure 1: The hierarchy of value types and reference types.

Reference types are all derived from the System.Object class, not from the derived System.ValueType. .NET stores reference types on the memory heap, rather than the stack, and include not only what you might think of as objects, that is, instances of your own custom classes (as well as the other couple of thousand .NET classes), but also more complex native data representations such as arrays and strings. Figure 1 illustrates these relationships.

This article discusses how casting reference types actually works, and how you can take advantage of casting when interacting with class instances. Understanding this diagram is important if you want to understand the differences between casting value types and casting reference types.

Implicit and Explicit Casting
.NET languages allow implicit casting of one type to another. For instance, if you assign an Integer value to a variable declared as a Double, you generate neither compiler nor runtime errors:

   Dim a As Integer   a = 125   Dim b As Double   b = a

The reason that this is permissible is that there is no loss of data (no rounding) to perform this cast. Other casts of value types can cause a loss of data, such as when you do the reverse, casting a Double value type as an Integer type. If the Double type is holding a value that includes a fraction, such as 10.25, this value will be rounded to 10 if it is recast as an Integer, losing the fractional portion.

   Dim b As Doubleb = 125.23Dim a As IntegerA = CInt(b)

You can easily see the practical effect of casting one data type to another, especially when it results in a change in the value being stored. Less obvious is whether or not the underlying structure being used to store the value is being changed. One might assume that this is the case, but how do you confirm this? (See Sidebar: Preventing Implicit Casts)

Both value type structures and object-based classes inherit from System.Object, and therefore inherit an implementation of the GetType() method that allows you to determine what kind of type you’re dealing with.

   Dim DoubleValue As DoubleConsole.WriteLine _      ("DoubleValue's type is: " + _          DoubleValue.GetType().ToString())   ' Shows that DoubleValue's type is   ' System.Double

Thus when you test this assumption you can see that casting an Integer value type to a Double value type (or any other cast to one of the value types?DateTime, Boolean, Double, etc.), does indeed result in a change not only to the value being stored, but to the actual type of the structure holding that value.

   Dim IntegerValue as IntegerIntegerValue = _      CInt(DoubleValue)Console.WriteLine _      ("IntegerValue's type is: " + _       IntegerValue.GetType().ToString())   ' Shows that IntegerValue's type is   ' System.Int32

Casting Objects
While casting value types, such as Integers, Double, Long, Boolean, Date, etc., is pretty straightforward, the very meaning of casting when applied to reference types such as Objects Strings, Arrays, Interfaces, etc., is less clear, as is the actual behavior of reference types and how you use these behaviors to your advantage in writing applications.

The very meaning of casting when applied to reference types?objects, strings, arrays, interfaces?is less clear, as is the actual behavior and how you would use these behaviors to your advantage in writing applications.

Consider (again) the simple class hierarchy diagram in Figure 1. If you create an Array, you can cast that Array as an Object.

   Dim array1 As string = _       {"Alpha", "Bravo", "Charlie", "Delta", _     "Echo", "Foxtrot"}     Dim o1 As Object     ' Do an implicit cast...     o1 = array1     ' Or do an explicit cast...     o1 = CType(array1, Object)
Figure 2: IntelliSense showing the members of an array cast as an Object.

As you can see in this code sample, you can execute either an explicit or implicit cast with Option Strict on?the reason is that you’re “down” casting, i.e., casting from a specific to a general type. System.Array is derived from System.Object, so an implicit cast from Array to Object is allowed. Note the use of the new CType() function, which you can use to cast dissimilar types by specifying two arguments: the type to be cast, and the target type of that cast. (See Sidebar: Boxing)

Based on previous experiments with value types, you might expect that casting reference types also results in a change to the underlying type used to hold the values. In the following example, calling o1.GetType() returns a type of “Object.” However, this code also shows that even though you have cast array1 as an Object, the target, o1 still has a data type of “System.String[]”, which indicates an Array type containing string values.

   Console.WriteLine("The type of o1 is: " + _        o1.GetType().ToString())   ' Displays System.String[]

If you try to write code to examine the elements of the array, cast as object o1, IntelliSense shows you (Figure 2) that there are no elements to examine. The only exposed member that a token cast as System.Object has is the GetType() method, so it is impossible to interact with it as an array.

So what would be the result if you re-cast the o1 type as an Array?

   Dim array2 As Arrayarray2 = _      CType(o1, Array)Console.WriteLine _      ("Type of re-cast array is " _      + array2.GetType.ToString())   Console.WriteLine( _     "Re-casting o1 as array...Contents are:")   For Each s In array2      Console.WriteLine(s)   Next

The “object” o1 is re-cast as an Array. GetType() again shows that the type is System.String[], an array of string elements. Note that in this case, you must use the CType() function to make an explicit cast, as this is the type of implicit cast that is prohibited by Option Strict. Also, notice that the original array elements? “Alpha,” “Bravo,” etc., are magically “restored.” What’s going on here?

What happens is that when you cast an array as an Object, all of the members of the original type survive the cast?they simply are no longer accessible. In terms of its interface, the new object looks like the cast?in this case, an Object. Casting an array as an Object has the effect of cloaking the members that belong only to the array.

A moment’s reflection on the different behavior between making copies of value types versus reference types makes this obvious. If you create an Integer type and a Double type, and copy the value stored in the Integer type to the Double type, you can then change the value of the Integer type without affecting the new copy that resides in the Double type:

   Dim a As Integer   Dim b As Double   a = 10   b = a   a = 20   Console.WriteLine("The value of a is {0} " + _        "and the value of b is {1}",a,b)

The preceding code will print the following to the console:

   "The value of a is 20 and the value of b is 10"
When you make a copy of a reference type by casting, you still have a pointer to the original object.

However, when you copy a reference type, you get not a true copy but a new token that points to the same object. Special steps are needed to actually clone a reference type. As a result, it shouldn’t be too surprising that when you make a copy of a reference type by casting, you still have a pointer to the original object.

How does casting affect a non-intrinsic class, a custom class of your own creation?

The code in Listing 1 creates two classes. The first is a generic “Watercraft” class with a public Propel() method. This class is then subclassed and specialized as a “Canoe” class. The Canoe class adds a Paddling() method and overrides the Propel() method to call the Paddling() method. The Propel() method of the Watercraft class writes the phrase “Watercraft is moving…” to the console, and the Paddling() method of the Canoe class writes “Canoe is paddling…” to the console.

You can then instantiate the Canoe class and call the Paddling() method, which is to be expected. What would happen, however, if you cast the Canoe object as a Watercraft? Can you still call the Paddling() method?

The answer is no. The new object behaves as if the Paddling() method doesn’t exist, yet the GetType() method confirms that what you have is still a Canoe. Based on this understanding, what should you expect from calling the Propel() method? The answer depends on exactly how the members of the Canoe class have been cloaked to make it appear to be a Watercraft. If the entire derived class has been peeled away then a call to Propel() would result in a “Watercraft is moving…” string at the console. If, on the other hand, what is done is that the unique, Canoe-class-only members are hidden from the new object created by the casting, you’ll get the “Canoe is paddling…” string.

What’s happening? The Paddling() method may not be visible, but the overridden Propel() method is executed and it successfully calls the “hidden” Paddling() method! Casting, then, allows you to get an object reference that exposes a subset of the class’s members. Can you cast one object type to that of an unrelated class? As one might expect intuitively, the answer is “No.” For instance, if you create another class hierarchy consisting of a Bicycle class and a derived MountainBike class, you could try the following:

   Dim canoe1 As Canoe = New Canoe()   Dim bike1 As MountainBikebike1 = _      CType(canoe1, MountainBike)

However, you’re immediately presented with a compiler error indicating that it is impossible to convert a canoe into a mountain bike (no surprise). Can you get clever and still fool the compiler? You can but it doesn’t do much good as the following code demonstrates:

   Dim bike1 As Object   bike1 = CType(canoe1, Object)   Dim bike2 As MountainBike   bike2 = CType(bike1, MountainBike)

This code compiles but generates an “Invalid cast exception error”.

If you review the class diagram in Figure 1, you can see what the governing principle is. You can cast an object as any one of its ancestors, and once cast, you can re-cast the resulting reference back to any class in the hierarchy chain including the original class used to define the original object. Of much more significance to note is that System.Object is the parent class, and as such, you can cast any object as an Object, the lowest common denominator (or lowest common ancestor) of all classes in the .NET Framework!

Casting and Interfaces
You may wonder what use all of this is to you as a developer. The ability to reduce the members of a class to a subset?a subset that it may share with other, different classes?allows you to work with a collection of such objects. As you know, to create the simplest of collections, an array, you must define the type of elements the array will be able to store. You may wish to store a group of different objects to an array, which would be impossible if you could not cast the objects into a common type.

For example, assume you have an Employee class with subclasses such as SalesPerson, Supervisor, CustomerServiceRep, and so on. You may wish to create an array-based collection of a group of these objects with access to a common Employee class member such as a CurrentPayRate() property. However, in their “native” state, this would not be possible. You cannot do the following:

   Dim Employees() As SalesPerson Or Supervisor_   Or CustomerServiceRep

However, you can do this:

   Dim Employees(2) As Employee   Employees(0) = CType(New _      SalesPerson(),"Employee")   Employees(1) = CType(New _   Supervisor(),"Employee")   Employees(2) = CType(New _   CustomerServiceRep(),"Employee")   Dim i As Integer   For i = 0 to UBound(Employees)        Console.WriteLine("Current rate of pay is "_         + Employees(i).CurrentPayRate.ToString()   End For

This works fine for a group of objects that may be based on the same superclass, or otherwise share a common ancestor. However, there may be disparate classes that you need to process as a collection, or pass as an argument to a method that do not share any common ancestor other than System.Object. How can you use casting to work around this? You can indeed cast such objects as a System.Object then recast them to their native types, but this may require more knowledge about the objects than you may possess in a given situation. Interfaces, however, are tailor-made to address this issue.

Any group of object classes can implement a common interface, and such an interface can be used in an object collection or passed as an argument by extracting the interface alone, using the CType() function to cast the object as it’s interface.

You may have noticed in the Watercraft class that it implements the ITopSpeed interface. The code for this interface is as follows:

   Interface ITopSpeed        Property TopSpeed() As Integer   End Interface

Each class that implements this interface has specific code to return a value from or store a value to a private field. Because the Watercraft class implements this interface, any derived class will inherit this interface. The Bicycle class and its derived class also implement the ITopSpeed interface, thus you can create an array to hold references to this interface and work with any members defined by this interface without regard to the fact that the interfaces may belong to vastly different classes. The code in Listing 2 shows how (in a very simple manner) this can be used:

Figure 3 shows the output from executing this code.

Figure 3: Using an interface reference to work with a collection of disparate objects.

It has always been important for programming languages to provide the ability to convert values from one type to another. .NET languages let you convert object references based on one class to another object reference that appears and behaves like an object based on another class.

If you understand and use casting, it is possible to usefully aggregate class instances.

If you understand and use casting it is possible to usefully aggregate class instances whether you want to access them as a collection or create generic black-box methods and functions that can accept any class instance cast as an object or an implemented interface. This could then re-cast the class instances back into their original type to access type members, or in the case of an interface, access the interface members directly.

For example, many intrinsic interfaces in the .NET Framework, such as IComparable.CompareTo() specify an argument of type Object, but to implement the interface method on a specific class, you can recast the object to the appropriate class to process class-specific members. You may think that the behavior of casting when applied to reference types may seem awkward or counter-intuitive, but once you understand them they make perfect sense and their utility starts to become apparent.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: