Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Casting Reference Types in Visual Basic .NET : Page 2

Casting objects (.NET reference types) is an important technique when attempting to aggregate any collection of dissimilar object instances. Understanding how casting works also illustrates the value of using interfaces when defining classes.

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!

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date