Recipe3.31.Disposing of Unmanaged Resources


Recipe 3.31. Disposing of Unmanaged Resources

problem

Your class references unmanaged resources such as some type of handle or manipulates a block of memory or a file via P/Invoke methods or 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.

Solution

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 shown in Example 3-23.

Example 3-23. Foo: A class that contains references to unmanaged code

 using System; using System.Collections; using System.IO; using System.Runtime.InteropServices; public class Foo : IDisposable {     [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 Foo( ) {}     // Replace SomeCOMObj with your COM object type.     private SomeCOMObj comObj = new SomeCOMObj( );     private FileStream fileStream = new FileStream(@"c:\test.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)         {             try             {                 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.                     Marshal.ReleaseComObject(comObj);                     GC.SuppressFinalize(this);                 }                 // Release unmanaged handle here.                 IntPtr prevCnt = new IntPtr( );                 ReleaseSemaphore(hSemaphore, 1, out prevCnt);             }             catch (Exception)             {                 hasBeenDisposed = false;                 throw;             }             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…                 // Call base class' Dispose method.                 base.Dispose(disposeManagedObjs);             }             catch (Exception)             {                 hasBeenDisposed = false;                 throw;             }                          hasBeenDisposed = true;         }     } } 

Whether this class directly contains any references to unmanaged resources, it should be disposed of as shown in the code.

Discussion

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 foreach loop guarantees that it will call the IDisposable.Dispose method if the object returned from the GetEnumerator method implements IDisposable.

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# finalizers 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. The GC.SuppressFinalize method removes the object from the fqueue, because it doesn't need specific code run for the finalizer; the memory can just be released. Calling this static method from within the Dispose method is critical to writing better performing classes.

Call the GC.SuppressFinalize method in the base class Dispose method when the overload of the Dispose method is passed true. 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 finalizer is also added to this class. The finalizer 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 finalizers 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: it clears the collection of all its items. This Clear method has nothing to do with releasing unmanaged resources or calling the Dispose method.

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 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.28.

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 disposedthat 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 ObjectDisposedExceptions) 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 finalizer is also inherited by the Bar class.

All Dispose methods should call their base class's Dispose method.


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 finalizer is the object's last line of defense for releasing unmanaged resources.

See Also

See Recipes 3.28 and 3.29; see the "Dispose Interface," "Using foreach with Collections," and "Implementing Finalize and Dispose to Clean up Unmanaged Resources" topics in the MSDN documentation.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net