Constructor Methods
In VB.NET, classes can implement a
special method that is always invoked as
an object is created. This method is called the constructor, and it is
always named New. We've seen this used before most notably in a
regular Windows form, where the New
method is used to hold any initialization code for the form.
The constructor method is an ideal location for such
initialization code, since it is always
run before any other methods are ever invoked and it is only ever run once
for an object. Of course, we can create many objects based on a class and the
constructor method will be run for each object that is created.
The constructor
method of a VB.NET class is similar to the Class_Initialize
event in previous versions of Visual Basic, but is far more powerful in VB.NET
since we can accept parameter values as input to the method.
We can implement a constructor in our classes as well
using it to initialize our objects as needed. This is as easy as implementing a
Public
method named New.
Add the following to our Person
class:
Public Sub New()
Phone("home") = "555-1234"
Phone("work") = "555-5678"
End
Sub
In this example, we're simply using the constructor method
to initialize the home and work phone numbers for any new Person
object that is created.
Parameterized Constructors
We can also use constructors to allow parameters to be passed to our
object as it is being created. This is done by simply adding parameters to the New method. For example, we can change the Person class as follows:
Public Sub New(ByVal Name As String, ByVal
BirthDate As Date)
mstrName = Name
mdtBirthDate = BirthDate
Phone("home")
=
"555-1234"
Phone("work")
=
"555-5678"
End Sub
With this change, any time a Person
object is created, we'll be provided with values for both the name and birth
date. This changes how we can create a new Person
object, however. Where we used
to have code such as:
Dim myPerson As New Person()
Now we will have code such as:
Dim myPerson As New Person("Peter",
"1/1/1960")
In fact, since our constructor expects these values, they
are mandatory any code wishing to create an instance of our Person
class must provide these values.
Fortunately, there are alternatives in the form of optional parameters and
method overloading (which allows us to create multiple versions of the same
method each accepting a different parameter list something we'll discuss
later in the chapter).
Constructors with Optional Parameters
In many cases, we may want our constructor to
accept parameter values for initializing new objects but we also want to have
the ability to create objects without providing those values. This is possible
through
method overloading, which we'll discuss later, or through the use of optional
parameters.
Optional parameters on a constructor method follow the same
rules as optional parameters for any other Sub
routine they must be the last
parameters in the parameter list and we must provide default values for the
optional parameters.
For instance, we can change our Person
class as shown:
Public Sub New(Optional ByVal Name As String = "", _
Optional ByVal BirthDate As Date = #1/1/1900#)
mstrName = Name
mdtBirthDate =
BirthDate
Phone("home")
=
"555-1234"
Phone("work")
=
"555-5678"
End Sub
Here we've changed both the Name
and BirthDate
parameters to be optional, and we are providing default values for both of
them. Now we have the option of creating a new Person
object with or without the parameter values:
Dim myPerson As New
Person("Peter", "1/1/1960")
or
Dim myPerson As New Person()
If we don't provide the parameter values then the default
values of an empty String and 1/1/1900
will be used and our code
will work just fine.
Termination and Cleanup
In the .NET environment, an object is destroyed and the
memory and resources it consumes are reclaimed when there are no references
remaining for the object.
As we discussed earlier in the chapter, when we are using
objects, our variables actually hold a reference or pointer to the object
itself. If we have code such as:
Dim myPerson As New Person()
we know that the myPerson
variable is just a reference
to the Person
object we created. If we also have code like this:
Dim anotherPerson As Person
anotherPerson = myPerson
we know that the anotherPerson
variable is also a
reference to the same object. This means
that this specific Person object is being referenced by two variables.
When there are no
variables left referencing an object, it can be terminated by the .NET runtime
environment. In particular, it is terminated and reclaimed by a mechanism
called garbage collection, which we'll discuss shortly.
Unlike COM (and thus
VB6), the .NET runtime does not use reference counting to determine when an
object should be terminated. Instead, it uses a scheme known as garbage
collection to terminate objects. This means that, in VB.NET, we do not have
deterministic finalization, so it is not possible to predict exactly when an
object
will be destroyed.
Before we get to garbage collection, however, let's review how
we can eliminate
references to an object.
We can explicitly remove a reference by setting our variable
equal to Nothing,
with code such as:
There are two schools of
thought as to whether we should still explicitly set variables to Nothing
even when they fall out of scope. On one hand, we can save writing extra lines
of code by allowing the variable to automatically be destroyed but, on the
other hand, we can explicitly show our intent to destroy the object by setting
it to Nothing
manually.
Perhaps most
important is the fact that the garbage collection mechanism will sometimes
reclaim our objects in the middle of our processing. This can only happen if
our code doesn't use the object later in the method. Setting the variable to
Nothing at the end of the method will prevent the garbage collection mechanism
from proactively reclaiming our objects.
We can also remove a
reference to an object by
changing the variable to reference a different object. Since a variable can
only point to one object at a time, it follows naturally that changing a
variable to point at another object must cause it to no longer point to the
first one. This means we can have code such as:
which causes the variable
to point to a brand new object thus releasing this reference to the prior
object.
These are examples of explicit dereferencing. VB.NET
also provides facilities for implicit dereferencing of objects when a
variable goes out of scope. For instance, if we have a variable declared within
a method, when that method completes the variable will be automatically
destroyed thus dereferencing any object to which it may have pointed. In
fact, any time a variable referencing an object goes out of scope, the
reference to that object is automatically eliminated.
This is illustrated by the following code:
Private Sub DoSomething()
Dim
myPerson As Person
myPerson = New Person()
End Sub
Even though we didn't explicitly set the value of myPerson
to Nothing,
we know that the myPerson variable will be destroyed when the method is
complete since it will fall out of scope. This process implicitly removes the
reference to the Person object created within the routine.
Of course, another scenario where objects become
dereferenced is when the application itself completes and is terminated. At
that point, all variables are destroyed and so, by definition, all object
references go away as well.
We discussed garbage collection and the Finalize
method in Chapter 3. When we discussed these concepts, we mentioned that there
was no automatic way to perform the cleanup when the final reference to an
object is released, although implementing the IDisposable
interface provides one solution. We'll investigate that solution now.
The IDisposable Interface
In some cases the Finalize behavior is not acceptable. If we have an
object that is using some expensive or limited resource such as a database
connection, a file handle, or a system lock we might need to ensure that the
resource is freed as soon as the object is no longer in use.
To accomplish this, we can implement a method to be called
by the client code to force our object to clean up and release its resources.
This is not a perfect solution, but it is workable. The thing to remember is
that this method is not called automatically by the .NET runtime environment,
but instead must be called directly by the code using the object.
The .NET framework provides the IDisposable
interface that formalizes the declaration of this cleanup method. We'll discuss
creating and working with multiple interfaces in detail later so, for now,
we'll just focus on the implementation of the Dispose
method from a cleanup perspective.
Any class that derives from System.ComponentModel.Component
automatically gains the IDisposable
interface. This includes
all of the forms and controls that
are used in a Windows Forms UI, as well as various other classes within the
.NET
framework. For most of our custom classes, however, we'll need to implement the
interface ourselves.
We can implement it in our Person
class by adding the following code to the top of the class:
Public Class Person
Implements IDisposable
This interface defines a single method Dispose
that we need to implement
in our class. It is implemented by adding the following code to the class:
Private Sub Dispose() Implements IDisposable.Dispose
colPhones = Nothing
End
Sub
In this case, we're using this method to release our
reference to the HashTable object that the colPhones
variable points to. While not strictly necessary, this illustrates how our code
can release other objects when the Dispose
method is called.
It is up to our client code to call this method at the
appropriate time to ensure that cleanup occurs. Typically, we'll want to call
the method as soon as we're done using the object.
This is not always as easy as it might sound. In particular,
an object may be referenced by more than one variable and just because we're
dereferencing the object from one
variable doesn't mean it has been dereferenced by all the other
variables. If we call the Dispose
method while other references remain our object may become unusable and may
cause errors when invoked via those other references. There is no easy solution
to this problem so careful design is required in the case that we choose to
use the IDispose
interface.
In our application's Form1
code, we use the OnLoad
method of the form to create an instance of the Person
object. In the form's OnClosed method, we may want to make sure to clean up by
disposing of the Person object. To do this, add the following code to the
form:
Private Sub Form1_Closed(ByVal
sender As Object, _
ByVal e As System.EventArgs) Handles MyBase.Closed
CType(mobjPerson, IDisposable).Dispose()
End
Sub
The OnClosed
method runs as the form is
being closed, and so it is an appropriate place to do
cleanup work.
Before we can dereference the Person
object, however, we can now call its Dispose
method. Since this method is
part of a secondary interface (something we'll discuss more later), we need to
use the CType()
method to access that specific interface in order to call the method:
CType(mobjPerson(), IDisposable).Dispose()
CType() allows us to indicate the specific interface by
which we want to access the object in this case the IDisposable
interface. Once we're using that interface, we can call the Dispose
method to cause the object to do any cleanup before we release our reference:
Once we've released the reference, we know that the garbage
collection mechanism will eventually find and terminate the object thus
running its Finalize
method. In the meantime, however, we've forced the object to do any cleanup
immediately, so its resources are not consumed during the time between our
release of the reference and the garbage collection terminating the object.