Casting and Data Type Conversions in VB.NET

Here's a fix for when a variable in a program just isn't the right type

This article is the first of a two part series that explains how data type conversion - that is, "casting" - happens in VB.NET both at the level of your source code and deeper down - the way things actually happen in memory. Part Two explains more about the three casting operators, including how fast they can perform the same casting.

Why Casting?

The usual reason is that some operation you need for your program just won't work without a specific data type.

For example, suppose you want to store both elements from a web page and numeric elements taken from a file in the same array. The problem is that arrays aren't flexible enough to allow this. To make it work, you have to "cast" all of the elements into a common type. (Probably String.)

(Real World Example: As I was writing this article, a reader asked a question that involved implicit conversion from a String found on a web page. It turned out that there was no direct conversion possible because the web page encoding was unknown to VB.NET. See Using Numbers from a Web Page in a VB program.)

Here's an example where casting is required using one of Microsoft's latest technologies, WPF.

Suppose you want the same Windows Forms subroutine to handle multiple events. You can do that by placing all of the events in a Handles clause in the Sub statement:


Private Sub myEventSub_Click( ... ) _
   Handles Control1.Click, Control2.Click ... etc.

But WPF's more complex event handling model requires a different technique. Multiple controls in a WPF logical control tree can all trigger the same event. So you need to extract the control you need from the WPF RoutedEventArgs parameter - in this case, it's the familiar "e" parameter that you see in all event subroutines - passed to the event subroutine.

(Don't worry if you don't understand any of this. The point is only to show that casting is a technique that you need no matter what VB.NET technology you're using.) To get that information, you can cast e to a local control declared in your subroutine.

In more detail, suppose you have a StackPanel with a lot of Button controls inside of it. You want the same event subroutine to handle all of the Buttons.


<StackPanel ButtonBase.Click="StackPanel_Click">
   <Button x:Name="Button1" />
   <Button x:Name="Button2" />
   <Button x:Name="Button3" />
</StackPanel>

You can cast e to a Button object to get the result you want. CType is the casting function used here:


Private Sub StackPanel_Click( _
   ByVal sender As System.Object, _
   ByVal e As System.Windows.RoutedEventArgs)
   Dim myButton As Button = CType(e.Source, Button)
   Console.WriteLine(myButton.Name)
End Sub

In general, when a VB.NET statement requires one type but you have another type instead, casting is the solution.

I remember a line from an old science fiction movie called "The Black Hole" where a little robot says, "You cannot pour a quart into a pint. If a pint holds a pint, it is doing the best that it can." That's essentially the idea behind the difference between widening and narrowing type conversions. A widening conversion is pouring a pint into a quart. A narrowing conversion is the opposite and the problem is that some of whatever is in the quart can spill on the floor.

VB.NET keeps track of whether a conversion is widening or narrowing and since VB.NET 2005, you have been able to use the compiler option ...

Option Strict On

... to disallow any narrowing conversion. According to Microsoft these are the widening conversions for standard data types:

Byte --> Byte, Short, Integer, Long, Decimal, Single, Double
Short --> Short, Integer, Long, Decimal, Single, Double
Integer --> Integer, Long, Decimal, Single, Double
Long --> Long, Decimal, Single, Double
Decimal --> Decimal, Single, Double
Single --> Single, Double
Double --> Double
Any enumerated type --> Its underlying integer type and any type to which it will widen
Char --> Char, String
Any type --> Object, any interface that it implements
Any derived type --> Any base type from which it is derived
Nothing --> Any data type or object type

The default for Option Strict is "Off" mainly for backward compatibility. Most authorities recommend that you turn it on.

To do that, place Option Strict On at the top of your code, before any other source code statements. If you turn Option Strict and Option Explicit off, then you can write programs with variables that behave a little like the old VB6 "variant" type. Microsoft calls this "typeless programming" and says it can lead to errors and inefficient code.

The Different Ways Data Type Conversion Can Happen

The first, and probably most common way that data type conversion can happen is implicit conversions. This is just a word that means, "the compiler noticed that you needed one, so it did it for you".

If you code ...


Dim a as Integer = 10
Dim b as String
b = a

... your code will include an implicit data type conversion from Integer to String. (This is also an example of a widening conversion.)

Explicit conversion uses a VB.NET supplied conversion keyword. You can use DirectCast, TryCast and a whole group of functions that begin with the letter "C" -- for example CDbl (convert to the Double type) and CBool (convert to a Boolean type). You can't convert anything to anything because sometimes, a conversion just doesn't make sense. For example, only String and Object can be converted to Char. Each conversion has specific rules. One benefit of these explicit conversions is that they are "localization aware". A Date, for example, will appear in the format specified in the location specified.

The CType(expression, typename) function includes all of the other conversion functions. But it will also convert an object, structure, class, or interface. You will see the word "coerce" used in these conversions. This was important in the WPF example earlier. The RoutedEventArgs type is a Class. In this case, the CType function coerces the type RoutedEventArgs type to a Button type.

DirectCast may seem like it's almost the same thing as CType, but it isn't.

You can, for example, substitute DirectCast into the WPF code above and it still works. The difference is that DirectCast "requires the run-time type of an object variable to be the same as the specified type". Sometimes, you will see this requirement written this way, "One type must inherit from or implement the other type". And another way that the requirement is sometimes written is, "DirectCast does not use the Visual Basic run-time helper routines for conversion." All these are just different sides of the same requirement. If the specified type and the run-time type of the expression are the same, however, the run-time performance of DirectCast is better than that of CType.

Here's an example showing how to code using the "run-time type".


Dim myInt As Integer = 10
Dim myObj As Object
myObj = myInt
If TypeOf myObj Is Integer Then
   Console.Write(
   "The run-time type of myObj is Integer")
End If

Often, a value is returned that is one of several inherited types and you just have to convert it to the correct type. For example, if you need to work with the individual controls in the collection of controls in a Form object, you may need to use TypeOf and DirectCast. Here's a code example.


For Each ctrl As Control In Me.Controls
    If TypeOf ctrl Is TextBox Then
        DirectCast(sender, TextBox).BackColor =
            Color.Firebrick
    End If
Next

There are two general categories of types in VB.NET: reference types and value types. Reference types inherit System.Object directly or through another reference type. Reference types are stored in a part of memory called the heap. Objects that are instances of classes are reference types. Because the String type is actually a collection of characters, it's a reference type too. Look up System.String in the Object Browser and you'll see that it's a Class that inherits directly from System.Object.

Value types only inherit System.Object implicitly through the System.ValueType type. Value types are stored in a different part of memory called the stack. Value types include Boolean and numeric values (Float, Long, Double, Integer), Date, and DateTime values. Structures are also value types. Look up Boolean in the Object browser and you'll see that it's a Structure.

.NET is considered to be more advanced than languages like C in part because everything ultimately inherits from the single type Object. In computer language theory, this is called a "unified type system".

Another difference between reference types (heap) and value types (stack) is that reference types are accessed through a memory address called a pointer (in other words, a "reference" to the actual location).

The pointer itself is actually saved on the stack. So when a variable is "boxed", a pointer is created to a memory location and saved on the stack. The actual value of the pointed to is in the memory location on the heap, however. When it's "unboxed", the value is found in memory and saved on the stack again.

Here's a code example showing boxing and unboxing:


Dim myInt as Integer = 10 ' myInt is a value type
Dim myObj as Object ' myObj is a reference type
myObj = myInt  ' myInt is boxed
Dim myInt2 as Integer ' myInt2 is also a value type
myInt2 = myObj ' myObj is unboxed

Most of this will be totally transparent to you most of the time and you won't have to think about it. But occasionally, you need to know what's really going on. For more about what's really going on, and specifically, more about the performance of the three casting operators, check out Part Two