GOTCHA 40 Implementing IDisposable isn t enough


GOTCHA #40 Implementing IDisposable isn't enough

Let's review key points from Gotcha #35, Gotcha #38, and Gotcha #39:

  • The call to Finalize() involves overhead, which you can avoid by calling GC.SuppressFinalize() in Dispose().

  • There is no guarantee when the Finalize() method will be called, so you may hold resources much longer than you intend.

For these reasons, it is better to use Dispose() instead of relying on Finalize().

That sounds good, except for one problem. Unfortunately, the author of a class has no way to force the user of an object to invoke the Dispose() method. It requires discipline on the part of the programmer to call Dispose() at the appropriate location in the code. What if he forgets to? You want the resources to be cleaned up anyway; doing it in Finalize() is better than not doing it at all. You know that Finalize() will be called eventually, you just don't know when.

So what code should you put in Finalize()? Remember that you should not touch any managed resources (Gotcha #36, "Releasing managed resources in Finalize( ) can wreak havoc"). But you do need to clean up unmanaged resources.

What code do you write in Dispose()? If you call GC.SuppressFinalize() in Dispose() (as you should), you won't be able to rely on Finalize() to clean up unmanaged resources; Dispose() will have to take care of them.

In addition, since Dispose() (unlike Finalize()) is not executed in the middle of a garbage-collection sweep, it can safely release other managed objects. So within Dispose() you take care of cleaning up both managed and unmanaged resources.

How do you free unmanaged resources in Dispose() and Finalize() without duplicating code? In Gotcha #38, "Depending on Finalize() can tie up critical resources," these two methods made identical calls to ReleaseComObject() (see Example 5-5).

The Dispose design pattern will help properly refactor Dispose() and Finalize(). (Refer to "Dispose Design Pattern" in the section "on the web" in the Appendix.)

This pattern stipulates that both Finalize() and IDisposable.Dispose() delegate to a third protected virtual/Overridable method. According to Microsoft's specification, this method should be called Dispose() as well, and should take one bool/Boolean parameter, which Microsoft calls disposing. This indicates whether the method has been called by IDisposable.Dispose() (true) or Finalize() (false). This protected Dispose() method does all resource cleanup. Whether the disposing argument is true or false, it cleans up unmanaged resources. But only if this argument is TRue, indicating that the object is still accessible, does it attend to managed resources.

The Dispose(bool disposing) method is protected because you don't want users of your class to call it directly, and you need to override it in any derived classes.

A better name could have been chosen for the Dispose(bool) method in the Dispose Design Pattern. It is less confusing if you can clearly see the difference between the methods named Dispose. Unfortunately, a number of classes in the .NET class library (e.g., System.EnterpriseServices.ServicedComponent), expect you to use the Dispose(bool) method. I'm sticking with the method names in the pattern to be consistent.


With this pattern, only classes at the top of your class hierarchies need to implement Finalize() and IDisposable.Dispose(). Those two methods call Dispose(false) and Dispose(true), respectively. Derived classes just implement the protected version of Dispose(). Because this method is defined as virtual/Overridable in the base class, and override/Overrides in all derived classes, the correct derived implementation will execute.

The full VB.NET prototype also includes the Overloads keyword, as follows:

    Protected Overloads Overrides Sub Dispose( _        ByVal disposing As Boolean) 


To complete the pattern, the derived class's Dispose() must invoke its base class's Dispose(); i.e., the protected version with the disposing parameter.

Example 5-7 and Example 5-8 illustrate the Dispose Design Pattern.

Example 5-7. Dispose design pattern (C#)

C# (DisposeFinalize)

 //Base.cs using System; public class Base : IDisposable {     private bool disposed = false;     private readonly int id;     public Base(int theID)     {         id = theID;     }     protected virtual void Dispose(bool disposing)     {         if (!disposed)         {     if (disposing)             {                 Console.WriteLine(                     "Base Cleaning up managed resources on {0}",                     id);         // Code to clean up managed resources             }             Console.WriteLine(                 "Base Cleaning up unmanaged resources on {0}", id);     // Code to clean up unmanaged resources         }         disposed = true;     }     public void Dispose()     {         Dispose(true);         GC.SuppressFinalize(this);     }     ~Base()     {         Console.WriteLine("*** Finalize called on Base {0}", id);         Dispose(false);     } } //Derived.cs using System; namespace DisposePattern {     public class Derived : Base     {         private bool disposed = false;         public Derived(int theID) : base(theID) {}         protected override void Dispose(bool disposing)         {             if (!disposed)             {                 try                 {             if (disposing)                     {                         Console.WriteLine(                             "Derived Cleaning up managed resources");                 // Code to clean up managed resources                     }                     Console.WriteLine(                         "Derived Cleaning up unmanaged resources");             // Code to clean up unmanaged resources                 }                 finally                 {             base.Dispose(disposing);                 }             }             disposed = true;         }     } } //Test.cs using System; namespace DisposePattern {     class Test     {         [STAThread]         static void Main(string[] args)         {             Derived object1 = new Derived(1);             Derived object2 = new Derived(2);             object1.Dispose();         }     } } 

Example 5-8. Dispose design pattern (VB.NET)

VB.NET (DisposeFinalize)

 'Base.vb Public Class Base     Implements IDisposable     Private disposed As Boolean = False     Private ReadOnly id As Integer     Public Sub New(ByVal theID As Integer)         id = theID     End Sub     Protected Overridable Sub Dispose(ByVal disposing As Boolean)         If Not disposed Then     If disposing Then                 Console.WriteLine( _                     "Base Cleaning up managed resources on {0}", _                     id)           ' Code to cleanup managed resources             End If             Console.WriteLine( _              "Base Cleaning up unmanaged resources on {0}", id)     ' Code to cleanup unmanaged resources         End If         disposed = True     End Sub     Public Sub Dispose() Implements System.IDisposable.Dispose         Dispose(True)         GC.SuppressFinalize(Me)     End Sub     Protected Overrides Sub Finalize()         MyBase.Finalize()         Console.WriteLine("*** Finalize called on Base {0}", id)         Dispose(False)     End Sub End Class 'Derived.vb Public Class Derived     Inherits Base     Private disposed As Boolean = False     Public Sub New(ByVal theID As Integer)         MyBase.New(theID)     End Sub     Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)         Try             If Not disposed Then                 If disposing Then                     Console.WriteLine( _                         "Derived Cleaning up managed resources")             ' Code to cleanup managed resources                 End If                 Console.WriteLine( _                     "Derived Cleaning up unmanaged resources")         ' Code to cleanup unmanaged resources             End If             disposed = True         Finally     MyBase.Dispose(disposing)         End Try     End Sub End Class 'Test.vb Module Module1     Sub Main()         Dim object1 As New Derived(1)         Dim object2 As New Derived(2)         object1.Dispose()     End Sub End Module 

Take a look at the Base class. It implements IDisposable, so it must provide a Dispose() method with no parameter. This method invokes the Dispose(bool disposing) method with true as its argument. The Finalize() method invokes Dispose(bool disposing) as well, but passes false instead of true. The Dispose(bool disposing) method cleans up unmanaged resources regardless of the parameter's value. It only cleans up managed resources if disposing is TRue, that is, only if it's been called by the no-parameter Dispose() method. If invoked by the Finalize() method, it does not touch any managed resources. See Gotcha #36, "Releasing managed resources in Finalize( ) can wreak havoc."

In the Derived class, you do not have to implement the Finalize() method or the no-parameter Dispose(). Instead, you simply override the Dispose() method that takes the bool/Boolean parameter. First you clean up managed resources, but only if the disposing parameter is true. Next, you take care of unmanaged resources. Finally, you call the corresponding Dispose() method of the Base class. The output from the above program is shown in Figure 5-7.

Figure 5-7. Output from Example 5-7


From the output, you can see that object1 is cleaned up using the Dispose() method and that its Finalize() is not called. Both managed and unmanaged resources are cleaned up properly here. On the other hand, object2 is cleaned up in the Finalize() method. Only the unmanaged resources are deleted in this case, as expected.

With this code, you still need to be concerned about a couple of problems. First, you may have to make some adjustments to the Dispose(bool) method for thread-safety in a multithreaded application. Second, it is possible for a user to call the Dispose() method on an object more than once, and to call other methods on an object after the call to Dispose(). In your methods, you need to check if the object has been disposed. If it has, you should raise an ObjectDisposedException.

As a final illustration of the Dispose Design Pattern, let's revisit the Wrapper class from Gotcha #38, "Depending on Finalize() can tie up critical resources" (see Example 5-5). In the previous version, both Finalize() and Dispose() call ReleaseComObject(). By moving this call to Dispose(bool disposing), you remove the duplication. The Wrapper class in this example has no managed resources and no base class below System.Object. Therefore, it does not call base.Dispose().

Example 5-9 shows the refactored implementation.

Example 5-9. Refactored Wrapper Class

C# (RefactoredWrapper)

 //Wrapper.cs using System; using ACOMCompLib; namespace FinalizePeril {     public class Wrapper : IDisposable     {         IMyComp comp = new MyCompClass();         bool disposed = false;         public int doSomething()         {             if (disposed)             {                throw new ObjectDisposedException(null);             }             int result;             comp.doSomething(out result);             return result;         }         protected virtual void Dispose(bool disposing)         {             if (!disposed)             {                // No managed resources to clean up                // Clean up unmanaged resources regardless of who called us                System.Runtime.InteropServices.Marshal.ReleaseComObject(comp);                GC.SuppressFinalize(this);                disposed = true;             }         }         ~Wrapper()         {     Dispose(false);         }         #region IDisposable Members         public void Dispose()         {     Dispose(true);         }         #endregion     } } 

VB.NET (RefactoredWrapper)

 'Wrapper.vb Imports ACOMCompLib Public Class Wrapper        Implements IDisposable     Dim comp As IMyComp = New MyCompClass     Private disposed As Boolean = False     Public Function doSomething() As Integer         If disposed = True Then            Throw New ObjectDisposedException(Nothing)         End If         Dim result As Integer         comp.doSomething(result)         Return result     End Function     Protected Overridable Sub Dispose(ByVal disposing As Boolean)         If Not disposed Then            ' No managed resources to clean up            ' Clean up unmanaged resources regardless of who called us            System.Runtime.InteropServices.Marshal.ReleaseComObject(comp)            GC.SuppressFinalize(Me)            disposed = True         End If     End Sub     Public Sub Dispose() Implements System.IDisposable.Dispose         Dispose(True)     End Sub     Protected Overrides Sub Finalize()         Try            Dispose(False)         Finally            MyBase.Finalize()         End Try     End Sub End Class 

IN A NUTSHELL

Follow the Dispose design pattern (with adjustments for thread-safety if needed), because it provides an effective way to clean up both managed and unmanaged resources.

SEE ALSO

Gotcha #35, "Writing Finalize( ) is rarely a good idea," Gotcha #36, "Releasing managed resources in Finalize( ) can wreak havoc," Gotcha #37, "Rules to invoke base.Finalize( ) are not consistent," Gotcha #38, "Depending on Finalize() can tie up critical resources," Gotcha #39, "Using Finalize() on disposed objects is costly," and Gotcha #41, "Using the Dispose Design Pattern doesn't guarantee cleanup."



    .NET Gotachas
    .NET Gotachas
    ISBN: N/A
    EAN: N/A
    Year: 2005
    Pages: 126

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