Interacting with Objects
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.