- Stream-Based Programming
- Dispose and Finalize
- Asynchronous Programming
- Summary
Dispose and Finalize
A second major design pattern that you'll find in the .NET Framework is that of dispose and finalize. Basically, you'll find this pattern in classes that acquire and hold on to resources such as file and window handles or network and database connections. This is the case because the Common Language Runtime (CLR) within which your .NET code runs implements nondeterministic finalization, meaning that the CLR ultimately decides when object instances are no longer able to be referenced and when those instances are deallocated in memory. The process of reclaiming memory by deallocating instances is referred to as garbage collection, or simply GC. This approach is obviously much different than the deterministic techniques used in previous versions of VB (in which setting an object to Nothing deallocated the object immediately and caused its Terminate event to run) or C++ (in which an object could be deallocated at any time, causing its destructor to run). Obviously, objects that hold on to resources such as file handles or database connections need a way to release those resources in a timely fashionhence the dispose and finalize design pattern.
The idea behind this pattern is that the client decides when the resources are no longer required and calls the Dispose or Close method on the class. This method then cleans up the resources and ensures that the object can no longer be used. However, adding a Dispose or Close method is not sufficient in and of itself because, if the client forgets to call it, the resource may not then be released, causing a leak in your application. To ensure that resources are cleaned up, your classes can implement a destructor that gets called automatically by the CLR when the GC process finally cleans up the instance. As a result, in your classes you'll want to implement a Dispose or Close method along with a destructor, as shown in Listing 2.
Listing 2: Implementing the Dispose and Finalize Pattern
Public MustInherit Class QuilogyDataAccess : Implements IDisposable Protected _disposed As Boolean = False Protected Overridable Sub Dispose(ByVal disposing As Boolean) If disposing Then ' Call dispose on any objects referenced by this object End If ' Release unmanaged resources End Sub Protected Overridable Sub Dispose() Implements IDisposable.Dispose Dispose(True) _disposed = True ' Take off finalization queue GC.SuppressFinalize(Me) End Sub Protected Overrides Sub Finalize() Me.Dispose(False) End Sub End Class Public Class Customers : Inherits QuilogyDataAccess Public Sub Close() Me.Dispose(True) End Sub Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then ' Call dispose on any objects referenced by this object End If ' Release unmanaged resources MyBase.Dispose(disposing) End Sub Public Sub GetCustomers() ' Make sure the object has not been disposed If MyBase._disposed Then Throw New ObjectDisposedException("Customers", "Has been closed") End If ' Do other work here End Sub End Class
In Listing 2, you'll notice that the QuilogyDataAccess class is a base (MustInherit, abstract in C#) class that ostensibly creates and holds on to a managed resource, such as a database connection, or an unmanaged resource, such as a file or window handle. As a result, this class exposes a Dispose method by implementing the IDisposable interface and includes a destructor implemented by overriding the Finalize method.
You'll notice that when the Dispose method inherited from the IDisposable interface is called, it calls a second Dispose method, passing it True. This second Dispose method cleans up the managed resources by calling their Dispose methods if passed True, and then proceeds to deallocate unmanaged resources. When it is finished, it returns and the original Dispose method then sets the protected disposed flag to True, to indicate that the class has already been disposed of. It then calls the static SuppressFinalize method of the GC class. This is done to prevent the Finalize method (the destructor) from running when the GC process next runs. Calling SuppressFinalize is simply an optimization that allows the instance to ultimately be deallocated sooner because all objects with a destructor are placed in a special queue that must first run their destructors before deallocating them.
Although it may seem strange that you need to make this differentiation, it is important because, if the GC process ends up running the Finalize method, you have no way of knowing whether the managed objects referenced by the class have already been finalized. This is the case because the order of finalization is not deterministic, regardless of the order in which the objects were deemed unreachable by the GC. As a result, you wouldn't want to call their Dispose methods if they had already been deallocated. You'll notice that the Finalize method simply executes the Dispose method, passing in False to ensure that unmanaged resources are cleaned up if the Dispose method is never executed and the Finalize method is being run by the GC process. Incidentally, the pattern shown in Listing 1 works with C# as well where the destructor is identified using a ~, as in this example:
~ QuilogyDataAccess() { this.Dispose(false); }
The other difference between C# and VB .NET is that destructors in C# can be called only by the GC process, not directly by client code; the Finalize method in VB .NET can be called by other code in the class or its descendants. This is why the destructor calls Dispose and not vice versa.
The Customers class is then derived from QuilogyDataAccess and can override the Dispose method to clean up its own resources by calling Dispose on any objects that it references before calling the Dispose method of the base class. As with the base class, the Dispose method of the derived class accepts an argument that determines whether managed resources are cleaned up in addition to unmanaged resources. By passing True into the method, as is done by the Close method, the class calls the Dispose methods of any managed objects that it references, in addition to deallocating unmanaged resources such as file and window handles.
You'll notice that the Customers class also exposes a public Close method that allows clients to clean up the resources explicitly. The Close method simply calls the Dispose method to do the work and can be used when the Close method makes more sense semantically. The GetCustomers method of the Customers class shows how a method can then check to see whether the object has previously been disposed of before allowing execution to continue. If the object has already been disposed of, you can throw the ObjectDisposedException. Note that if you want to re-create the resources if a method such as GetCustomers is called after the object has been disposed, you would also need to call the ReRegisterForFinalize method of the GC class to make sure that the instance will be finalized by the GC eventually if Close or Dispose is not called.
Clients using the Customers class can then instantiate the class and work with it as normal, making sure to call the Close method when finished or casting to the IDisposable interface like this:
Dim c As New Customers() ' Do other work here c.GetCustomers() c.Close()
Or
Dim c As New Customers() Dim d As IDisposable ' Do other work here c.GetCustomers() d = c d.Dispose()
Alternatively, the C# language includes the using statement that automatically casts to the IDisposable interface and calls the Dispose method when the block is exited, like this:
using (Customers c = new Customers()) { // Do other work here c.GetCustomers(); }
It should be mentioned that the compiler will allow the using statement only when the class to which it refers implements the IDisposable interface.