16.5 Implement a Disposable Class


Problem

You need to create a class that references unmanaged resources and provide a mechanism for users of the class to free those unmanaged resources deterministically.

Solution

Implement the System.IDisposable interface, and release the unmanaged resources when client code calls the IDisposable.Dispose method.

Discussion

An unreferenced object continues to exist on the heap and consume resources until the garbage collector releases the object and reclaims the resources. The garbage collector will automatically free managed resources (such as memory), but it won't free unmanaged resources (such as file handles and database connections) referenced by managed objects. If an object contains data members that reference unmanaged resources, the object must free those resources explicitly.

One solution is to declare a destructoror finalizerfor the class; destructor is a C# term equivalent to the more general .NET term finalizer. Prior to reclaiming the memory consumed by an instance of the class, the garbage collector calls the object's finalizer. The finalizer can take the necessary steps to release any unmanaged resources. Unfortunately, because the garbage collector uses a single thread to execute all finalizers, use of finalizers can have a detrimental effect on the efficiency of the garbage collection process, which will affect the performance of your application. In addition, you can't control when the runtime frees unmanaged resources because you can't call an object's finalizer directly, and you have only limited control over the activities of the garbage collector using the System.GC class.

As an alternative to using finalizers, the .NET Framework defines the Dispose pattern as a means to provide deterministic control over when the runtime frees unmanaged resources. To implement the Dispose pattern, a class must implement the IDisposable interface, which declares a single method named Dispose . In the Dispose method, you must implement the code necessary to release any unmanaged resources.

Instances of classes that implement the Dispose pattern are called disposable objects . When code has finished with a disposable object, it calls the object's Dispose method to free unmanaged resources, still relying on the garbage collector to eventually release the object's managed resources. It's important to understand that the runtime doesn't enforce disposal of objects; it's the responsibility of the client to call the Dispose method. However, because the .NET Framework class library uses the Dispose pattern extensively, C# provides the using statement to simplify the correct use of disposable objects with the using statement. The following code shows the structure of a using statement:

 using (FileStream fileStream = new FileStream("SomeFile.txt", FileMode.Open)) {     // Do something with the fileStream object } 

Here are some points to consider when implementing the Dispose pattern:

  • Client code should be able to call the Dispose method repeatedly with no adverse effects.

  • In multithreaded applications, it's important that only one thread execute the Dispose method concurrently. It's normally the responsibility of the client code to ensure thread synchronization, although you could decide to implement synchronization within the Dispose method.

  • The Dispose method should not throw exceptions.

  • Because the Dispose method does all necessary clearing up, there's no need to call the object's finalizer. Your Dispose method should call the GC.SuppressFinalize method to ensure the finalizer isn't called during garbage collection.

  • Implement a finalizer that calls the Dispose method as a safety mechanism in case client code doesn't call Dispose correctly. However, avoid referencing managed objects in finalizers because you can't be certain of the object's state.

  • If a disposable class extends another disposable class, the Dispose method of the child must call the Dispose method of its base class. Wrap the child's code in a try block and call the parent's Dispose method in a finally clause to ensure execution.

  • Other methods and properties of the class should throw a System.ObjectDisposedException if client code attempts to execute a method on an already disposed object.

The DisposeExample class contained in the following listing demonstrates a common implementation of the Dispose pattern:

 using System; // Implement the IDisposable interface public class DisposeExample : IDisposable {     // Private data member to signal if the object has already been      // disposed     bool isDisposed = false;     // Private data member that holds the handle to an unmanaged resource     private IntPtr resourceHandle;     // Constructor     public DisposeExample() {         // Constructor code obtains reference to unmanaged resource.         // resourceHandle = ... }     // Destructor / Finalizer. Because Dispose calls GC.SuppressFinalize,     // this method is only called by the garbage collection process if     // the consumer of the object does not call Dispose as they should.     ~DisposeExample() {         // Call the Dispose method as opposed to duplicating the code to          // clean up any unmanaged resources. Use the protected Dispose         // overload and pass a value of "false" to indicate that Dispose is         // being called during the garbage collection process, not by         // consumer code.         Dispose(false);             }     // Public implementation of the IDisposable.Dispose method, called     // by the consumer of the object in order to free unmanaged resources     // deterministically.      public void Dispose() {         // Call the protected Dispose overload and pass a value of "true"         // to indicate that Dispose is being called by consumer code, not         // by the garbage collector.         Dispose(true);         // Because the Dispose method performs all necessary clean up,         // ensure the garbage collector does not call the class destructor.         GC.SuppressFinalize(this);     }     // Protected overload of the Dispose method. The disposing argument      // signals whether the method is called by consumer code (true), or by      // the garbage collector (false).     protected virtual void Dispose(bool disposing) {         // Don't try to Dispose of the object twice         if (!isDisposed) {             // Determine if consumer code or the garbage collector is              // calling. Avoid referencing other managed objects during             // finalization.             if (disposing) {                 // Method called by consumer code. Call the Dispose method                 // of any managed data members that implement the                  // IDisposable interface.                  //              }             // Whether called by consumer code or the garbage collector,             // free all unmanaged resources and set the value of managed              // data members to null.             // Close(resourceHandle);         }         // Signal that this object has been disposed.         isDisposed = true;     }     // Before executing any functionality, ensure that Dispose has not      // already been executed on the object.     public void SomeMethod() {         // Throw an exception if the object has already been disposed         if (isDisposed) {             throw new ObjectDisposedException("DisposeExample");         }         // Execute method functionality.         //      }         public static void Main() {         // The using statement ensures the Dispose method is called         // even if an exception occurs.         using (DisposeExample d = new DisposeExample()) {             // Do something with d         }     } } 



C# Programmer[ap]s Cookbook
C# Programmer[ap]s Cookbook
ISBN: 735619301
EAN: N/A
Year: 2006
Pages: 266

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