Your class references unmanaged resources such as some type of handle, or it manipulates a block of memory or a file via P/Invoke methods or your class uses a COM object that requires some cleanup method to be called before it is released. You need to make sure that the resources are released properly and in a timely manner. In a garbage-collected environment, such as that used by the Common Language Run-time (CLR), you cannot assume either will happen.
Implement the dispose design pattern, which is specific to .NET. The class that
contains a reference to the unmanaged resources is shown here as
Foo
. This object contains references to a COM
object called SomeCOMObj
, a
FileStream
object called
FStream
, and an ArrayList
that
may or may not contain references to unmanaged resources. The source
code is:
using System; using System.Collections; using System.IO; [DllImport("Kernel32.dll", SetLastError = true)] private static extern IntPtr CreateSemaphore(IntPtr lpSemaphoreAttributes, int lInitialCount, int lMaximumCount, string lpName); [DllImport("Kernel32.dll", SetLastError = true)] private static extern bool ReleaseSemaphore(IntPtr hSemaphore, int lReleaseCount, out IntPtr lpPreviousCount); public class Foo : IDisposable { public Foo( ) {} // Replace SomeCOMObj with your COM object type private SomeCOMObj comObj = new SomeCOMObj( ); private FileStream fileStream = new FileStream(@"c: est.txt", FileMode.OpenOrCreate); private ArrayList aList = new ArrayList( ); private bool hasBeenDisposed = false; private IntPtr hSemaphore = IntPtr.Zero; // Unmanaged handle // Protect these members from being used on a disposed object public void WriteToFile(string text) { if(hasBeenDisposed) { throw (new ObjectDisposedException(this.ToString( ), "Object has been disposed")); } UnicodeEncoding enc = new UnicodeEncoding( ); fileStream.Write(enc.GetBytes(text), 0, text.Length); } public void UseCOMObj( ) { if(hasBeenDisposed) { throw (new ObjectDisposedException(this.ToString( ), "Object has been disposed")); } Console.WriteLine("GUID: " + comObj.GetType( ).GUID); } public void AddToList(object obj) { if(hasBeenDisposed) { throw (new ObjectDisposedException(this.ToString( ), "Object has been disposed")); } aList.Add(obj); } public void CreateSemaphore( ) { // Create unmanaged handle here hSemaphore = CreateSemaphore(IntPtr.Zero, 5, 5, null); } // The Dispose methods public void Dispose( ) { Dispose(true); } protected virtual void Dispose(bool disposeManagedObjs) { if (!hasBeenDisposed) { if (disposeManagedObjs) { // Dispose all items in an array or ArrayList foreach (object obj in aList) { IDisposable disposableObj = obj as IDisposable; if (disposableObj != null) { disposableObj.Dispose( ); } } // Dispose managed objects implementing IDisposable fileStream.Close( ); // Reduce reference count on RCW while (Marshal.ReleaseComObject(comObj) > 0); GC.SuppressFinalize(this); } // Release unmanaged handle here IntPtr prevCnt = new IntPtr( ); ReleaseSemaphore(hSemaphore, 1, out prevCnt); hasBeenDisposed = true; } } // The destructor ~Foo( ) { Dispose(false); } // Optional Close method public void Close( ) { Dispose( ); } }
The following class inherits from Foo
:
// Class inherits from an IDisposable class public class Bar : Foo { //... private bool hasBeenDisposed = false; protected override void Dispose(bool disposeManagedObjs) { if (!hasBeenDisposed) { try { if(disposeManagedObjs) { // Call Dispose/Close/Clear on any managed objects here... } // Release any unmanaged objects here... } finally { // Call base class' Dispose method base.Dispose(disposeManagedObjs); hasBeenDisposed = true; } } } }
Whether this class directly contains any references to unmanaged resources, it should be disposed of as shown in the code.
The dispose design pattern allows any unmanaged
resources held by an object to be cleaned up from within the
managed environment. This pattern
is flexible enough to allow unmanaged resources held by the
disposable object to be cleaned up explicitly (by calling the
Dispose
method) or implicitly (by waiting for the
garbage collector to call the destructor).
Finalizers are a
safety net to clean up objects when you forget to do it.
This design pattern should be used on any base class that has derived types that hold unmanaged resources. This indicates to the inheritor that this design pattern should be implemented in their derived class as well.
All the code that needs to be written for a disposable object is
written within the class itself. First, all disposable types must
implement the
IDisposable
interface.
This interface contains a single method, Dispose
,
which accepts no parameters and returns void
. The
Dispose
method is overloaded to accept a Boolean
flag indicating whether any managed objects referenced by this object
should also be disposed. If this parameter is
true
, managed objects referenced by this object
will have their Dispose
method called, and
unmanaged resources are released; otherwise, only unmanaged resources
are released.
The IDisposable.Dispose
method will forward its
call to the overloaded Dispose
method that accepts
a Boolean flag. This flag will be set to true
to
allow all managed objects to attempt to dispose of themselves as well
as to release unmanaged resources held by this object.
The IDisposable
interface is very important to
implement. This interface allows the using
statement to take advantage of the dispose pattern. A
using
statement that operates on the
Foo
object is written as follows:
using (Foo f = new Foo( )) { f.WriteToFile("text"); }
Always implement the IDisposable
interface on
types that contain resources that need to be disposed or otherwise
explicitly closed or released. This allows the use of the
using
keyword and aids in self-documenting the
type.
A foreach
loop will also make use of the
IDisposable
interface, but in a slightly different
manner. After each iteration of this loop, the
Dispose
method is called via the enumerator type
of the object being enumerated. The enumerator type is usually a
nested class that implements IEnumerator
, and, in
this case, would also implement IDisposable
. The
foreach
loop guarantees that it will call the
Dispose
method on the enumerator object to allow
each individually enumerated object to be disposed of properly.
The overloaded Dispose
method that accepts a
Boolean flag contains a static method call to
GC.SuppressFinalize
to force the garbage collector
to remove this object from the fqueue, or
finalization queue. The fqueue allows the garbage collector to run C#
destructors at a point after the object has been freed. However, this
ability comes at a price: it takes many garbage collection cycles to
completely collect an object with a destructor. If the object is
placed on the fqueue in generation 0, the object will have to wait
until generation 1 is collected, which could be some time, since it
usually takes 10 generation 0 collections before generation 1 is
collected. The GC.SuppressFinalize
method prevents
the need for the object to stay in memory for all of these garbage
collection cycles. Calling this static method from within the
Dispose
method is critical to writing better
performing classes.
Always call the GC.SuppressFinalize
method in the
base class Dispose
method. Doing so will allow
your object to be taken off of the finalization queue in the garbage
collector allowing for earlier collection. This will help prevent
memory retention and will help your application’s
performance.
A destructor is also added to this class. The destructor contains
code to call the overloaded Dispose
method,
passing in false
as its only argument. Note that
all cleanup code should exist within the overloaded
Dispose
method that accepts a Boolean flag. All
other methods should call this method to perform any necessary
cleanup. The destructor will pass a false
value
into the Dispose
method to prevent any managed
objects from being disposed. Remember, the destructors run in their
own thread. Attempting to dispose of objects that may have already
been collected or are about to be collected could have serious
consequences for your code, such as resurrecting an object into an
undefined state. It is best to prevent any references to other
objects while the destructor is running.
It is possible to add a
Close
or even a Clear
method to
your class to be called as well as the Dispose
method. Several classes in the FCL use a Close
or
Clear
method to clean up unmanaged resources:
FileStream.Close( ) StreamWriter.Close( ) TcpClient.Close( ) MessageQueue.Close( ) SymmetricAlgorithm.Clear( ) AsymmetricAlgorithm.Clear( ) CryptoAPITransform.Clear( ) CryptoStream.Clear( )
Each of these classes also contains a Dispose
method. The Clear
method usually calls the
Dispose
method directly. There is a problem with
this design. The Clear
method is used extensively
throughout the FCL for classes such as ArrayList
,
Hashtable
, and other collection type classes.
However, the Clear
method of the collection
classes performs a much different task; instead of calling the
IDisposable.Dispose
method, it clears the
collection of all its items. This Clear
method has
nothing to do with releasing unmanaged resources or calling the
Dispose
method.
Another problem is the confusion with the Close
,
Clear
, and Dispose
methods of
the CryptoStream
class. The
Close
method simply flushes the pending data and
attempts to close the underlying stream object. The
Clear
method forwards its call on to the
Dispose
method. The Dispose
method cleans up this object, but does not close the underlying
stream object. If you look at the base Stream
class, it has an implementation of IDisposable
,
and it will close the stream when you dispose it. But
CryptoStream
replaces this implementation with its
own that fails to close the stream, and which
doesn’t call back into the Stream
base class’s Dispose
implementation. So it’s entirely inconsistent. From
this, we can conclude that to completely clean up a
CryptoStream
object, we must first call
Close
and then either call
Clear
or Dispose
. When in
doubt, always default to calling the Dispose
method on an object.
Consider not implementing a Close
method unless it
will be obvious to the user or inheritor of this class what it is
for, or if you are deriving from a type such as
Stream
, which does not give you a choice, since
the Stream
class contains no implementation for
this method. Never implement a Clear
method that
will be used to dispose your object. Instead, use the commonly
recognized Dispose
method. Otherwise, your code
will not operate in a consistent manner with the disposable classes
within the FCL.
This implementation does not follow the dispose design pattern. To
follow this pattern, the Close
method should
simply forward its call on to the
IDisposable.Dispose
method. In addition, the
Clear
method is never mentioned in the dispose
design pattern, so avoid using a Clear
method for
anything other than to remove elements from a collection type. As a
note, this inappropriate usage of the Clear
method
is unique to the cryptography classes. All other classes in the FCL
seem to use it correctly.
The overloaded Dispose
method that accepts a
Boolean flag will contain all of the logic to release unmanaged
resources from this object as well as possibly calling
Dispose
on types referenced by this object. In
addition to these two actions, this method can also reduce the
reference count on any COM objects that are referenced by this
object. The static Marshal.ReleaseComObject
method
will decrement the reference count by one on the COM object reference
passed in to this method:
Marshal.ReleaseComObject(comObj);
To force the reference count to go to zero, allowing the COM object to be released and its Runtime Callable Wrapper (RCW) to be garbage collected, you could write the following code:
while (Marshal.ReleaseComObject(comObj) > 0);
Take great care when forcing the reference count to zero in this manner. If another object is using this COM object, the COM object will be released out from under this other object. This can easily destabilize a system. For more information on using this method, see Recipe 3.30.
Any callable method/property/indexer (basically, any nonprivate
method except for the Dispose
and
Close
methods and the constructor(s) and the
destructor) should throw the
ObjectDisposedException
exception if it is called after the
object has been disposed—that is, after its
Dispose
method has been called. A private field
called hasBeenDisposed
is used as a Boolean flag
to indicate whether this object has been disposed; a
true
confirms that it has been disposed. This flag
is checked to determine whether this object has been disposed at the
beginning of every method/property/indexer. If it has been disposed,
the ObjectDisposedException
is thrown. This
prevents the use of an object after it has been disposed and
potentially placed in an unknown state.
Disposable objects should always check to see if they have been
disposed in all of their public methods, properties, and indexers. If
a client attempts to use your object after it has been disposed, an
ObjectDisposedException
should be thrown. Note
that a Dispose
method can be called multiple times
after this object has been disposed without having any side effects
(including the throwing of
ObjectDisposedException
s) on the object.
Any classes inheriting from Foo
need not implement
the IDisposable
interface; it is implied from the
base class. The inheriting class should implement the
hasBeenDisposed
Boolean flag field and use this
flag in any methods/properties/indexers to confirm that this object
has been disposed. Finally, a Dispose
method is
implemented that accepts a Boolean flag and overrides the same
virtual method in the base class. This Dispose
method does not have to call the
GC.SuppressFinalize(this)
static method; this is
done in the base class’s Dispose
method.
The IDisposable.Dispose
method should not be
implemented in this class. When the Dispose
method
is called on an object of type Bar
, the
Foo.Dispose
method will be called. The
Foo.Dispose
method will then call the overridden
Bar.Dispose(bool)
method, which, in turn, calls
its base class Dispose(bool)
method,
Foo.Dispose(bool)
. The base
class’s destructor is also inherited by the
Bar
class.
If the client code fails to call the Dispose
or
Close
method, the destructor will run and the
Dispose(bool)
method will still be called, albeit
at a later time. The destructor is the object’s last
line of defense for releasing unmanaged resources.
See Recipe 3.29 and Recipe 3.30; see the “Dispose Interface,” “Using foreach with Collections,” and “Implementing Finalize and Dispose to Clean Up Unmanaged Resources” topics in the MSDN documentation.