With all the changes to the way we declare, construct, and implement classes, it makes sense that there are also some changes in the way we interact with objects. These changes impact on how we instantiate objects, reference and dereference objects, and how we use early and late binding techniques.
Object Declaration and Instantiation
The most obvious change in the way we
work with objects comes as we try to create them and work with our object references.
VB.NET doesn’t use the CreateObject
statement for object creation. CreateObject
was an outgrowth of VB’s relationship with COM, and since VB.NET doesn’t use
COM, it has no use for CreateObject
.
Technically VB.NET can use COM objects through an interoperability mechanism. This is discussed in Chapter 9. However, in typical .NET programming, COM doesn’t enter the picture.
New Statement
VB.NET relies on the
New
statement for all object creation. We can use New
in a number of different locations within our code – all of them perfectly valid.
The most obvious is to declare an object variable and then create an instance of the object in an instance of the class:
Dim
obj As TheClass
obj
= New TheClass()
We can shorten this by combining the declaration of the variable with the creation of the instance:
Dim
obj As New TheClass()
In VB6 this was a very poor thing to do, as it had both negative performance and maintainability effects. However, in VB.NET there is no difference between our first example and this one, other than that our code is shorter.
Keep in mind that the scope of our variable
comes into play here. If we declare a variable within a block structure, that
variable will only be valid within that block structure. In many cases we’ll
want to declare the variable within the scope of our method, but possibly create
instances of the object within a block structure such as a Try...End
Try
or loop structure. In such a case, combining the
declaration with the instantiation may be inappropriate.
Another variation on the declaration and instantiation theme is:
Dim
obj As TheClass = New TheClass()
Again, this both declares a variable and creates an instance of the class for our use. This syntax is perhaps more useful when working with inheritance or with multiple interfaces. We might declare the variable to be of one type – say an interface – and instantiate the object based on a class that implements that interface:
Dim
obj As MyInterface = New TheClass()
We can employ more complex syntax at times also. Suppose that we have a method that requires an object reference. We can create an instance of the object right in the call to the method:
DoSomething(New
TheClass())
This calls the DoSomething
method, passing a new instance of TheClass
as a parameter. This new object will only exist for the duration of this one
method call. When the method completes, the object will be automatically dereferenced
by the .NET runtime.
Remember that dereferencing an object doesn’t mean it is immediately destroyed. As we discussed earlier, objects are only destroyed when the .NET garbage collection process runs through and cleans up orphaned objects.
This can be even more complex. Perhaps,
instead of needing an object reference, our method needs a String
.
We can provide that String
value from a method on our object – instantiating the object and calling the
method all in one shot:
DoSomething(New TheClass().GetStringData())
Obviously we 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 enhancing it.
No Set Keyword
Notice that nowhere
do we use the Set
statement when working with objects. In VB6, any time we worked with an object
reference we had to use the Set
command – differentiating objects from any other data type in the language.
In VB.NET, objects are not treated differently
from any other data type, and so we can use direct assignment for objects just
like we do with Integer
or String
data types. The Set
command is no longer valid in VB.NET. See Chapter 3 for more details.
Dereferencing Objects
In
VB6, we’d dereference an object by setting our object reference to Nothing
.
The same is true in VB.NET:
Dim
obj As TheClass
obj
= New TheClass()
obj
= Nothing
The effect of this statement is different in VB.NET, however. As we discussed earlier, VB.NET does not use reference counting to terminate objects, instead relying on a garbage collection mechanism. In VB6, when no more variables held a reference to an object, that object was immediately destroyed. In VB.NET this is not true – the object will be destroyed when the garbage collection process discovers that the object has no references. That is something that may happen seconds or even minutes after the last reference is removed.
This doesn’t eliminate the value of dereferencing objects however. If we have a long-running algorithm, it is a good practice to explicitly dereference objects within the process – thus allowing the garbage collector to remove them when possible. As long as our code retains a reference to an object, that object will remain in memory and will not be garbage collected.
Early vs. Late Binding
One of the strengths of VB has long been that we had access to both early and late binding when interacting with objects.
Early binding means that our code directly interacts with the object – knowing its data type ahead of time and thus being able to very efficiently interact with the object. Early binding allows the IDE to use IntelliSense to aid our development efforts and it allows the compiler to ensure that we are referencing methods that do exist and that we are providing the proper parameter values.
Late binding means that our code interacts with an object dynamically at run-time. This provides a great deal of flexibility since our code literally doesn’t care what type of object it is interacting with as long as the object supports the methods we 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 we get unprecedented flexibility.
VB.NET
continues this tradition, providing support for both early and late binding
as we work with
our objects.
By default, all objects are early bound.
The IDE and compiler enforce this as long as Option
Strict On
is set, and this is the default. However,
if we set Option
Strict
Off
at the top of a source file (as discussed in Chapter
3), we open the door for late binding throughout the code in that file.
Use of the Object Type
Late
binding occurs when the compiler can’t determine the type of object we’ll be
calling. This level of ambiguity is achieved through the use of the Object
data type. A variable of data type 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 MyMethod
method that accepts no parameters:
Option Strict Off
Module LateBind
Public Sub DoSomething(obj
As Object)
obj.MyMethod()
End Sub
End Module
If the object passed into this routine
doesn’t have a MyMethod
method that accepts no parameters, then a run-time error will result. Thus,
it is recommended that any code that uses late binding you should always provide
error trapping:
Option Strict Off
Module LateBind
Public Sub DoSomething(obj
As Object)
Try
obj.MyMethod()
Catch
‘
do something appropriate given failure to call the method
End Try
End Sub
End Module
While late binding is flexible, it can be error prone and it is slower than early bound code. To make a late bound method call, the .NET runtime must dynamically determine if the target object actually has a method that matches the one we’re calling, and then it must invoke that method on our behalf. This takes more time and effort than an early bound call where the compiler knows ahead of time that the method exists and can compile our code to make the call directly.
Late Binding and Reflection
The
.NET Framework supports a concept known as reflection.
This is the ability to write code that examines other .NET code to determine
its composition. Reflection is supported by the System.Reflection
namespace.
Reflection allows us to write code that discovers the classes within an assembly and the methods, properties and events exposed by those classes. We can then use reflection to create instances of those classes and call those methods. This entire process can be very dynamic – much like late binding.
In fact, VB.NET uses reflection to implement late binding on our behalf. Rather than forcing us to write the code that uses reflection to find and invoke a method, VB.NET handles this for us when we use late binding coding techniques.
We could implement a limited form of reflection
within VB6 by using the typelib
DLL. The functions in this DLL allowed us to dynamically discover the classes
and methods in a COM DLL, and then invoke them. Of course COM components were
described with IDL – a rather inaccurate description of the component. In .NET,
assemblies are described by metadata that accurately describes each assembly,
making reflection a much more robust solution.
Use of the CType Function
Whether we are using
late binding or not, it can be useful to pass object references around using
the Object data type
– converting them to an appropriate type when we need to interact with them.
This is done using the CType
function, allowing us to use a variable of type Object
to make an early bound method call:
Module
LateBind
Public Sub DoSomething(obj As Object)
CType(obj, TheClass).MyMethod()
End Sub
End
Module
Even though the variable we’re working
with is of type Object
– and thus any calls to it will be late bound – we are using the CType
method to temporarily convert the variable into a specific type, in this case
the type TheClass
.
This technique is often called casting. If we think of each interface or class type as a mold, we can cast an object of one type into the mold of another class or interface.
The CType function can be very useful when working with objects that implement multiple interfaces, since we can reference a single object variable through the appropriate type as needed. For instance, if we have an object of type TheClass that also implements MyInterface, we can use that interface with the following code:
Dim
obj As TheClass
obj
= New TheClass
CType(obj,
MyInterface).DoSomething()
In this way we can make early bound calls to other interfaces on an object without needing to declare a new variable of the interface type as we had to do in VB6.
Comments