Home > Articles > Programming > Windows Programming

  • Print
  • + Share This
This chapter is from the book

Rule 2-7: Beware of Class_Terminate

Two of the most heavily used classes in VB are probably Connection and Recordset, members of the ADO object model. Like most object-oriented classes, these two classes have destructors similar to Class_Terminate. In other words, they have methods that are automatically triggered when an instance of the class is about to be destroyed. Destructor methods are typically used to clean up before an object's state is lost forever, which would seem like the perfect place to handle things like saving changes to a database. So why is it, then, that Connection and Recordset have explicit Close methods that we have to call ourselves?

The answer is that some resources are too important to leave open until the client (or the run-time environment) gets around to triggering the object's destructor.22 In other words, in VB, the destructor is triggered when an object is no longer referenced by any client:

Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
 .
 .
 .
Set rs = Nothing '** destructor is triggered at this point, assuming           '**  we didn't pass the reference to anyone else

This occurs when all references have been set to Nothing. For database classes like Connection and Recordset, which may be allocating memory and setting locks in the database, unnecessary delay in performing cleanup may waste precious resources and may hurt performance. Hence the explicit Close method:

Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
rs.Open ...
 .
 .
 .
rs.Close     '** cleanup performed here
Set rs = Nothing '** destructor triggered here

Even though the client is responsible for calling Close (and thus may forget), its use can be documented as necessary for correct behavior. Regardless, it provides a solution to the problem of timely cleanup for those able to use it properly.

What does this mean to you? First of all, as a consumer of objects, you must be careful to use other classes properly. Look for methods entitled Close or Dispose, and be sure to call them as soon as you are done using that object. In particular, be careful to call these in your error handlers as well. For example, here's the proper way to ensure that both a connection and a recordset are closed, even in the presence of errors:

Public Sub SomeTask()
	On Error Goto errHandler
	Dim dbConn As ADODB.Connection
	Dim rs As ADODB.Recordset

	Set dbConn = New ADODB.Connection
	Set rs = New ADODB.Recordset
	 .
	 .
	 .
	rs.Close : dbConn.Close
	Set rs = Nothing : Set dbConn = Nothing
	Exit Sub

errHandler:
	If rs Is Nothing Then
	Else
		If rs.State <> adStateClosed Then rs.Close
		Set rs = Nothing
	End If

	If dbConn Is Nothing Then
	Else
		If dbConn.State <> adStateClosed Then dbConn.Close
		Set dbConn = Nothing
	End If
	
	Err.Raise ...
End Sub

Second, as a producer of classes, you need to decide whether VB's Class_Terminate event is sufficient for your cleanup needs. If not, then you need to incorporate an explicit Close or Dispose method in your class design.

The current convention is to provide a Close method if your design allows an object to be reopened or reused after it has been closed. Otherwise, provide a Dispose method, which implies to your clients that the object is no longer usable once Dispose has been called. Implementing these methods is easy; the hard part is deciding when your classes need them.

The obvious examples are classes that open and hold on to operating system or other resources: files, shared memory, network connections, and ADO Connection and Recordset objects. If you find yourself opening these types of resources in your class's Class_Initialize event (or in an explicit Open method) and accessing them via private class variables, then you most likely need a Close or Dispose method. For example, you may design a data access class that automatically logs every access via a private ADO connection:

'** class module: CDataAccess
Option Explicit

Private dbLog As ADODB.Connection '** for logging accesses

Public Sub Open()
	Set dbLog = New ADODB.Connection
	dbLog.Open "<proper connection string>"
End Sub
 .
 .
 .
Public Sub Close()
	dbLog.Close
	Set dbLog = Nothing
End Sub

Private Sub Class_Terminate()
	Close '** in case client forgets...
End Sub

Of course, keep in mind that although maintaining a dedicated logging connection may be efficient, it is certainly wasteful of an important (and usually limited) resource. In fact, we do not recommend the previous design for objects that may live in the middle tier, and thus need to scale (e.g., see rule 5-3). However, if such a design is appropriate in your case, then remember to think twice before relying solely on Class_Terminate for cleanup.

  • + Share This
  • 🔖 Save To Your Account