In the .NET environment, and within Visual Basic in particular, you use objects all the time without even thinking about it. Every control on a form - in fact, every form - is an object. When you open a file or interact with a database, you are using objects to do that work.
Objects are created using the New keyword, indicating that you want a new instance of a particular class. There are numerous variations on how or where you can use the New keyword in your code. Each one provides different advantages in terms of code readability or flexibility.
The most obvious way to create an object is to declare an object variable and then create an instance of the object:
Dim obj As TheClass obj = New TheClass()
The result of this code is that you have a new instance of TheClass ready for use. To interact with this new object, you use the obj variable that you declared. The obj variable contains a reference to the object, a concept explored later.
You can shorten the preceding code by combining the declaration of the variable with the creation of the instance, as illustrated here:
Dim obj As New TheClass()
Tip | In previous versions of Visual Basic, this substitution was a bad idea because it had negative performance and maintainability effects. However, in Visual Basic 2005, there is no difference between the first example and this one, other than that the latter code is shorter. |
The preceding code both declares the variable obj as datatype TheClass and creates an instance of the class, immediately creating an object that you can use. Another variation on this theme is as follows:
Dim obj As TheClass = New TheClass()
Again, this both declares a variable of datatype TheClass and creates an instance of the class. It is up to you how you create these instances, as it really a matter of style.
This third syntax example provides a great deal of flexibility while remaining compact. Though it is a single line of code, it separates the declaration of the variable’s datatype from the creation of the object.
Such flexibility is very useful when working with inheritance or multiple interfaces. You might declare the variable to be of one type - say, an interface - and instantiate the object based on a class that implements that interface. You’ll revisit this syntax when interfaces are covered in detail in Chapter 4.
So far you’ve been declaring a variable for new objects, but sometimes you may simply need to pass an object as a parameter to a method, in which case you can create an instance of the object right in the call to that method:
DoSomething(New TheClass())
This calls the DoSomething method, passing a new instance of TheClass as a parameter. This can be even more complex. Perhaps, instead of needing an object reference, your method needs an Integer. You can provide that Integer value from a method on the object:
Public Class TheClass Public Function GetValue() As Integer Return 42 End Function End Class
You can then instantiate the object and call the method all in one shot, thus passing the value returned from the method as a parameter:
DoSomething(New TheClass().GetValue())
Obviously, you need to carefully weigh the readability of such code against its compactness. At some point, having more compact code can detract from readability, rather than enhance it.
Typically, when you work with an object, you are using a reference to that object. Conversely, when you are working with simple datatypes, such as Integer, you are working with the actual value, rather than a reference. Let’s explore these concepts and see how they work and interact.
When you create a new object using the New keyword, you store a reference to that object in a variable, as shown here:
Dim obj As New TheClass()
This code creates a new instance of TheClass. You gain access to this new object via the obj variable. This variable holds a reference to the object. You might then do something like this:
Dim another As TheClass another = obj
Now, you have a second variable, another, which also has a reference to the same object. You can use either variable interchangeably, as they both reference the exact same object. Remember that the variable you have is not the object itself but just a reference or pointer to the object.
When you are done working with an object, you can indicate that you’re through with it by dereferencing the object. To dereference an object, simply set the object reference to Nothing:
Dim obj As TheClass obj = New TheClass() obj = Nothing
Once any or all variables that reference an object are set to Nothing, the .NET runtime can tell that you no longer need that object. At some point, the runtime will destroy the object and reclaim the memory and resources consumed by the object. You can find more information on the garbage collector in Chapter 5.
Between the time that you dereference the object and the time that the .NET Framework gets around to actually destroying it, the object simply sits in the memory, unaware that it has been dereferenced. Right before .NET destroys the object, the Framework calls the Finalize method on the object (if it has one). The Finalize method is discussed in Chapter 4.
One of the strengths of Visual Basic has long been that it provides access to both early and late binding when interacting with objects.
Early binding means that code directly interacts with an object by directly calling its methods. Because the Visual Basic compiler knows the object’s datatype ahead of time, it can directly compile code to invoke the methods on the object. Early binding also enables the IDE to use IntelliSense to aid development efforts by enabling the compiler to ensure that you are referencing methods that exist and are providing the proper parameter values.
Late binding means that your code interacts with an object dynamically at runtime. This provides a great deal of flexibility because the code doesn’t care what type of object it is interacting with as long as the object supports the methods you want to call. Because the type of the object isn’t known by the IDE or compiler, neither IntelliSense nor compile-time syntax checking is possible, but in exchange you get unprecedented flexibility.
If you enable strict type checking by using Option Strict On in the project properties dialog or at the top of the code modules, then the IDE and compiler enforce early binding behavior. By default, Option Strict is turned off, so you have easy access to the use of late binding within the code. Chapter 2 discusses Option Strict. You can change this default directly in Visual Studio 2005 by selecting Tools Options from the VS menu. You are then presented with the Options dialog, shown in Figure 3-1. Expanding the Projects and Solutions node reveals the VB defaults. Feel free to change any of these default settings.
Figure 3-1
Late binding occurs when the compiler can’t determine the type of object that you’ll be calling. This level of ambiguity is achieved through the use of the Object datatype. A variable of datatype Object can hold virtually any value, including a reference to any type of object. Thus, code such as the following could be run against any object that implements a DoSomething method that accepts no parameters:
Option Strict Off Module LateBind Public Sub DoWork(ByVal obj As Object) obj.DoSomething() End Sub End Module
If the object passed into this routine does not have a DoSomething method that accepts no parameters, then an exception will be thrown. Thus, it is recommended that any code that uses late binding always provide exception handling:
Option Strict Off Module LateBind Public Sub DoWork(ByVal obj As Object) Try obj.DoSomething() Catch ex As MissingMemberException ' do something appropriate given failure ' to call this method End Try End Sub End Module
Here, the call to the DoSomething method has been put in a Try block. If it works, then the code in the Catch block is ignored; but in the case of a failure, the code in the Catch block is run. You need to write code in the Catch block to handle the case in which the object does not support the DoSomething method call. This Catch block only catches the MissingMemberException, which indicates that the method doesn’t exist on the object.
While late binding is flexible, it can be error prone and is slower than early-bound code. To make a late-bound method call, the .NET runtime must dynamically determine whether the target object actually has a method that matches the one you’re calling. It must then invoke that method on your behalf. This takes more time and effort than an early-bound call whereby the compiler knows ahead of time that the method exists and can compile the code to make the call directly. With a late-bound call, the compiler has to generate code to make the call dynamically at runtime.
Whether you are using late binding or not, it can be useful to pass object references around using the Object datatype, converting them to an appropriate type when you need to interact with them. This is particularly useful when working with objects that use inheritance or implement multiple interfaces, concepts discussed in Chapter 4.
If Option Strict is turned off, which is the default, then you can write code using a variable of type Object to make an early-bound method call:
Module LateBind Public Sub DoWork(obj As Object) Dim local As TheClass local = obj local.DoSomething() End Sub End Module
This code uses a strongly typed variable, local, to reference what was a generic object value. Behind the scenes, Visual Basic converts the generic type to a specific type so that it can be assigned to the strongly typed variable. If the conversion can’t be done, you get a trappable runtime error.
The same thing can be done using the CType function. If Option Strict is enabled, then the previous approach won’t compile, and the CType function must be used. Here is the same code making use of CType:
Module LateBind Public Sub DoWork(obj As Object) Dim local As TheClass local = CType(obj, TheClass) local.DoSomething() End Sub End Module
This code declares a variable of type TheClass, which is an early-bound datatype that you want to use. The parameter you’re accepting, though, is of the generic Object datatype, so you use the CType() method to gain an early-bound reference to the object. If the object isn’t of type TheClass, then the call to CType() fails with a trappable error.
Once you have a reference to the object, you can call methods by using the early bound variable, local. This code can be shortened to avoid the use of the intermediate variable. Instead, you can simply call methods directly from the datatype:
Module LateBind Public Sub DoWork(obj As Object) CType(obj, TheClass).DoSomething() End Sub End Module
Even though the variable you’re working with is of type Object and therefore any calls to it will be late bound, you use the CType method to temporarily convert the variable into a specific type - in this case, the type TheClass.
Tip | If the object passed as a parameter is not of type TheClass, you get a trappable error, so it is always wise to wrap this code in a Try...Catch block. |
As Chapter 4 discusses, the CType function can also be very useful when working with objects that implement multiple interfaces. When an object has multiple interfaces, you can reference a single object variable through the appropriate interface as needed.
Another function that is very similar to CType is DirectCast. DirectCast also converts values of one type into another type. It works in a more restrictive fashion than CType, but the trade-off is that it can be somewhat faster than CType:
Dim obj As TheClass obj = New TheClass DirectCast(obj, ITheInterface).DoSomething()
This is similar to the last example with CType, illustrating the parity between the two functions. There are differences, however. First, DirectCast works only with reference types, whereas CType accepts both reference and value types. For instance, CType can be used in the following code:
Dim int As Integer = CType(123.45, Integer)
Trying to do the same thing with DirectCast would result in a compiler error, as the value 123.45 is a value type, not a reference type.
Second, DirectCast is not as aggressive about converting types as CType. CType can be viewed as an intelligent combination of all the other conversion functions (such as CInt, CStr, and so on). DirectCast, on the other hand, assumes that the source data is directly convertible, and it won’t take extra steps to convert the data.
As an example, consider the following code:
Dim obj As Object = 123.45 Dim int As Integer = DirectCast(obj, Integer)
If you were using CType this would work, as CType uses CInt-like behavior to convert the value to an Integer. DirectCast, however, will throw an exception because the value is not a directly convertible o Integer.
A function similar to DirectCast is TryCast. TryCast converts values of one type into another type, but unlike DirectCast, if it can’t do the conversion, then TryCast doesn’t throw an exception. Instead, TryCast simply returns Nothing if the cast can’t be performed. TryCast only works with reference values; it cannot be used with value types such as Integer or Boolean.
Using TryCast, you can write code like this:
Module LateBind Public Sub DoWork(obj As Object) Dim temp As TheClass = TryCast(obj) If temp Is Nothing Then ' the cast couldn't be accomplished ' so do no work Else temp.DoSomething() End If End Sub End Module
If you aren’t sure whether a type conversion is possible, it is often best to use TryCast. This function avoids the overhead and complexity of catching possible exceptions from CType or DirectCast and still provides you with an easy way to convert an object to another type.