New Object-Oriented Capabilities in VB.NET

Object Lifecycle

Object Lifecycle

In VB6, objects had a clearly defined and well-understood life cycle – a set of events that we always knew would occur over the life of an object. We were guaranteed the following:

Event

Description

Sub Main

Would run as the component was loaded, before an object was created (optional)

Class_Initialize

Would run before any other code in our object; called by the runtime as the object was being created

Event

Description

Class_Terminate

Would run after any other code in our object; called by the runtime as the object was being destroyed

With VB.NET, objects also have a lifecycle, but things are not quite the same as in the past. In particular, we no longer have the same concept of a component-level Sub Main that runs as a DLL is loaded, and the concept of the Class_Terminate event changes rather substantially. However, the concept behind the Class_Initialize event is morphed into a full-blown constructor method that accepts parameters.

Thus, in VB.NET, we are only guaranteed the following:

Event

Description

New

Would run before any other code in our object; called by the runtime as the object was being created

This is quite a change so let’s discuss the details further.

Construction

Object construction is triggered any time we create a new instance of a class. This is done using the New keyword – a level of consistency that didn’t exist with VB6 where we got to choose between New and CreateObject.

Sub Main

Since VB6 was based on COM, creating an object could trigger a Sub Main procedure to be run. This would happen the first time an object was created from a given component – often a DLL. Before even attempting to create the object, the VB6 runtime would load the DLL and run the Sub Main procedure.

The .NET Common Language Runtime doesn’t treat components quite the same way, and so neither does VB.NET. This means that no Sub Main procedure is called as a component is loaded. In fact, Sub Main is only used once – when an application itself is first started. As further components are loaded by the application, only code within the classes we invoke is called.

It wasn’t that wise to rely on Sub Main even in VB6, since that code would run prior to all the error handling infrastructure being in place. Bugs in Sub Main were notoriously difficult to debug in VB6. If we do have to use code that relies heavily on the Sub Main concept for initialization, we’ll need to implement a workaround in VB.NET.

This can be done easily by calling a central method from the constructor method in each class. For instance, we might create a centrally available method in a module such as:

Public Module CentralCode
  Private blnHasRun As Boolean
  Public Sub Initialize()
    If Not blnHasRun Then
      blnHasRun = True
      ‘ Do initialization here
    End If
  End Sub
End Module

This routine is designed to only run one time, no matter how often it is called. We can then use this method from within each constructor of our classes. For example:

Public Class TheClass
  Public Sub New()
    CentralCode.Initialize()
    ‘ regular class code goes here
  End Sub
End Class

While this is a bit of extra work on our part, it does accomplish the same effect we’re used to with a VB6-style Sub Main routine.

New Method

Like the situation with Sub Main, Class_Initialize is called before any other code in a VB6 class. Again, it is called before the error handling mechanism is fully in place, making debugging very hard; errors show up at the client as a generic failure to instantiate the object. Additionally, Class_Initialize accepts no parameters – meaning there is no way in VB6 to initialize an object with data as it is created.

VB.NET eliminates Class_Initialize in favor of full-blown constructor methods, which have full error handling capabilities and do accept parameters. This means we can initialize our objects as we create them – a very important and powerful feature. The constructor method in VB.NET is SubNew. The simplest constructor method for a class is one that accepts no parameters – quite comparable to Class_Initialize:

Public Class TheClass
  Public Sub New()
    ‘ initialize object here
  End Sub
End Class

With this type of constructor, creating an instance of our class is done as follows:

Dim obj As New TheClass()

This example is directly analogous to creating a VB6 object with code in Class_Initialize.

However, more often than not we’d prefer to actually initialize our object with data as it is created. Perhaps we want to have the object load some data from a database, or perhaps we want to provide it with the data directly. Either way, we want to provide some data to the object as it is being created.

This is done by adding a parameter list to the New method:

Public Class TheClass
  Public Sub New(ByVal ID As Integer)
    ‘ use the ID value to initialize the object
  End Sub
End Class

Now, when we go to create an instance of the class, we can provide data to the object:

Dim obj As New TheClass(42)

To increase flexibility we might want to optionally accept the parameter value. This can be done in two ways – through the use of the Optional keyword to declare an optional parameter, or by overloading the New method. To use the Optional keyword, we simply declare the parameter as optional:

Public Sub New(Optional ByVal ID As Integer = -1)
  If ID = -1 Then
    ‘ initialize object here
  Else
    ‘ use the ID value to initialize the object
  End If
End Sub

This approach is far from ideal, however, since we have to check to see if the parameter was or wasn’t provided, and then decide how to initialize the object. It would be clearer to just have two separate implementations of the New method – one for each type of behavior. This is accomplished through overloading:

Public Overloads Sub New()
  ‘ initialize object here
End Sub
Public Overloads Sub New(ByVal ID As Integer)
  ‘ use the ID value to initialize the object
End Sub

Not only does this approach avoid the conditional check and simplify our code, but it also makes the use of our object clearer to any client code. The overloaded New method is shown by IntelliSense in the VS.NET IDE, making it clear that New can be called both with and without a parameter.

In fact, through overloading we can create many different constructors if needed – allowing our object to be initialized in a number of different ways.

Constructor methods are optional in VB.NET. The only exception being when we’re using inheritance and the parent class has only constructors that require parameters. We’ll discuss inheritance later in the chapter.

Termination

In VB6 an object was destroyed when its last reference was removed. In other words, when no other code had any reference to an object, the object would be automatically destroyed – triggering a call to its Class_Terminate event. This approach was implemented through reference counting – keeping a count of how many clients had a reference to each object – and was a direct product of VB’s close relationship with COM.

While this behavior was nice – since we always knew an object would be destroyed immediately and we could count on Class_Terminate to know when – it had its problems. Most notably, it was quite easy to create circular references between two objects, which could leave them running in memory forever. This was one of the few (but quite common) ways to create a memory leak in VB6.

To be fair, the problem was worse prior to VB6. In VB6, circular references are only a problem across components. Objects created from classes within the same component would be automatically destroyed in VB6, even if they had a circular reference. Still, the circular reference problem exists any time objects come from different components. The issue is non-trivial and has created a lot of headaches for VB developers over the years.

The clear termination scheme used in VB6 is an example of deterministic finalization. It was always very clear when an object would be terminated.

Unlike COM, 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. Let’s discuss garbage collection and the termination of VB.NET objects in more detail.

Garbage Collection

In .NET, reference counting is not part of the infrastructure. Instead, objects are destroyed through a garbage collection mechanism. At certain times (based on specific rules), a task will run through all of our objects looking for those that no longer have any references. Those objects are then terminated; the garbage collected.

This means that we can’t tell exactly when an object will really be finally destroyed. Just because we eliminate all references to an object doesn’t mean it will be terminated immediately. It will just hang out in memory until the garbage collection process gets around to locating and destroying it. This is an example of nondeterministic finalization.

The major benefit of garbage collection is that it eliminates the circular reference issues found with reference counting. If two objects have references to each other, and no other code has any references to either object, the garbage collector will discover and terminate them, whereas in COM these objects would have sat in memory forever.

There is also a potential performance benefit from garbage collection. Rather than expending the effort to destroy objects as they are dereferenced, with garbage collection this destruction process typically occurs when the application is otherwise idle – often decreasing the impact on the impact on the user. However, garbage collection may also occur with the application is active in the case that the system starts running low on resources.

We can manually trigger the garbage collection process through code:

System.GC.Collect()

This process takes time however, so it is not the sort of thing that should be done each time we want to terminate an object. It is far better to design our applications in such a way that it is acceptable for our objects to sit in memory for a time before they are finally terminated.

Finalize Method

The garbage collection mechanism does provide some functionality comparable to the VB6 Class_Terminate event. As an object is being terminated, the garbage collection code will call its Finalize method – allowing us to take care of any final cleanup that might be required:

Protected Overrides Sub Finalize()
  ‘ clean up code goes here
End Sub

This code uses both the Protected scope and Overrides keyword – concepts we’ll discuss later as we cover inheritance. For now it is sufficient to know that this method will be called just prior to the object being terminated by the garbage collection mechanism – somewhat like Class_Terminate.

However, it is critical to remember that this method may be called long after the object is dereferenced by the last bit of client code (perhaps even minutes later).

Implementing a Dispose Method

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. By convention, this method is typically named Dispose:

Public Sub Dispose()
  ‘ clean up code goes here
End Sub

It is up to our client code to call this method at the appropriate time to ensure cleanup occurs. Again, the specific name of this method is up to us, though within the .NET system class libraries the convention is to use the name Dispose.

At this point we’ve largely covered the changes in behavior between VB6 and VB.NET in terms of creating classes and objects. Let’s move on and see how the substantial new inheritance feature works.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“There are only two kinds of languages: the ones people complain about and the ones nobody uses” - Bjarne Stroustrup