Saturday, July 26, 2008

Suppress Finalization

After the Dispose method has been called on an object, you should suppress calls to the Finalize method by invoking the GC.SuppressFinalize method as a measure of performance optimization. Note that you should never change the order of calls in the finalization context (first Dispose(true) and then GC.SupressFinalize) to ensure that the latter gets called if and only if the Dispose method has completed its operation successfully.

The following code illustrates how to implement both the Dispose and Finalize pattern for a class.

   public class Base: IDisposable
{
private bool isDisposed = false;

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if(!isDisposed)
{
if (disposing)
{
// Code to dispose the managed resources
// held by the class
}
}
// Code to dispose the unmanaged resources
// held by the class
isDisposed = true;
base.Dispose(disposing);
}
~Base()
{
Dispose (false);
}
}
You should not reimplement IDisposable for a class that inherits from a base class in which IDispose has already been implemented. The following code snippet may help you understand this concept:

   public class Base: IDisposable
{
private bool isDisposed = false;

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if(!isDisposed)
{
if (disposing)
{
// Code to dispose managed resources
// held by the class
}
}
// Code to dispose unmanaged resources
// held by the class
isDisposed = true;
base.Dispose(disposing);
}
~Base()
{
Dispose (false);
}
}

public class Derived: Base
{
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Code to cleanup managed resources held by the class.
}

// Code to cleanup unmanaged resources held by the class.

base.Dispose(disposing);
}
// Note that the derived class does not // re-implement IDisposable
}
In the preceding code, what if the Dispose method were to throw an exception? In that case, the Finalize method would exit prematurely, and the memory would never be reclaimed. Hence, in such situations, it is advisable to wrap the Dispose method in a try-catch block. This will prevent finalization exceptions from orphaning the object.

Note the following points when implementing disposable types:

  • Implement IDisposable on every type that has a finalizer
  • Ensure that an object is made unusable after making a call to the Dispose method. In other words, avoid using an object after the Dispose method has been called on it.
  • Call Dispose on all IDisposable types once you are done with them
  • Allow Dispose to be called multiple times without raising errors.
  • Suppress later calls to the finalizer from within the Dispose method using the GC.SuppressFinalize method
  • Avoid creating disposable value types
  • Avoid throwing exceptions from within Dispose methods
In a managed environment, the GC takes care of freeing unused objects. In contrast, in unmanaged languages such as C, developers had to release unused objects explicitly that were created dynamically in the heap. However, a proper understanding of both the Dispose and Finalize methods goes a long way toward designing efficient applications.

No comments: