Garbage collection, in general, is the process of cleaning up the memory and resources used by an object when it is no longer needed. For instance, in C++, you write a destructor to take care of this. There are two tasks that the destructor typically performs:
deletes other objects to which the object holds pointers
closes handles to resources
.NET, on the contrary, provides automatic garbage collection. It largely removes the burden of memory management from the programmer. But what does automatic garbage collection really do? It just reclaims the memory used by objects. What about the cleanup of resources?
When I say resources here, I mean unmanaged resources: those that .NET will not be able to release automatically. For example, resources created while interacting with a COM component or while using PInvoke
to access some Win32 API. You, the programmer, have to write code to properly dispose of these resources.
The Finalize()
method is provided for this purpose. When an object is garbage-collected, the CLR takes care of memory-related cleanup and calls the object’s Finalize()
method, giving it an opportunity to release any unmanaged resources.
In VB.NET, you have to actually write the Finalize()
method. In C#, you don’t. Instead, you write a specialized pseudo-destructor with the ~
NameOfYourClass
()
syntax, as in C++. But don’t confuse the C# pseudo-destructor with the C++ destructor. The generated MSIL does not contain any destructor. The C# compiler creates the Finalize()
method using the code you write in the specialized pseudo-destructor.
Throughout the rest of this chapter, whenever I refer to the "Finalize()
method,” I am referring to the Finalize()
method in VB.NET and the pseudo-destructor in C#.
The CLR is responsible for reclaiming memory; your object’s Finalize()
method releases unmanaged resources. This appears reasonable and straightforward, but a number of practical issues surface during development. This chapter focuses on concerns related to garbage collection, and how to write code that handles it effectively.
An object is said to be accessible if traversing from a reference on the stack will lead you to it, directly or indirectly. When an object becomes inaccessible, it is ready for garbage collection, since no part of your code needs to access its content. When it is actually removed is up to the garbage collector. If this object implements a Finalize()
method, it is executed at the time of its cleanup by the CLR.
The state change an object undergoes during the cleanup phase is shown in Figure 5-1. It first goes from accessible (A
) to inaccessible (I
). While in this “zombie” state, it occupies memory, but you cannot execute any of its methods or access any of its fields or properties. (But sometimes it may look like you can. See Gotcha #36, "Releasing managed resources in Finalize() can wreak havoc." The object may remain in this inaccessible state anywhere from fractions of seconds to hours (or longer), depending on the garbage collector and the memory usage of the application. When the garbage collector eventually decides to clean the object up, it inspects it to see if the Finalize()
method needs to be executed. If so, the object is brought to the resurrected state (R
) and becomes accessible once again. Once the Finalize()
method has run, the object goes to the Inaccessible-no-need-to-finalize state (IF
). Then the memory occupied by the object is reclaimed and the object becomes non-existent (N
).
Jeffrey Richter explains the CLR garbage-collection mechanism in great detail. Refer to "Garbage Collection" in the section "on the web" in the Appendix.
If the object does not have a Finalize()
method, the cleanup will be faster. It can go from the inaccessible state (I
) to the non-existent state (N
) directly, as shown in Figure 5-1 by the dashed line labeled with a question mark.
Should you implement the Finalize()
method? Consider Example 5-1.
Example 5-1. Code with Finalize()
✗C# (Finalize)
using System; namespace WhyNoFinalize { public class SomeClass { private SomeOtherClass ref1; public SomeClass(SomeOtherClass givenObject) { ref1 = givenObject; } ~SomeClass() { ref1 = null; } } }
✗VB.NET (Finalize)
Public Class SomeClass Private ref1 As SomeOtherClass Public Sub New(ByVal givenObject As SomeOtherClass) ref1 = givenObject End Sub Protected Overrides Sub Finalize() ref1 = Nothing MyBase.Finalize() End Sub End Class
In this example, SomeClass
holds a reference to an instance of SomeOtherClass
. The Finalize()
method of SomeClass
sets the reference ref1
to null
/Nothing
. Is this necessary?
No, it isn’t. Whenever an instance becomes inaccessible, so do all of the other objects that are accessible only through that instance. So, when an object of SomeClass
becomes unreachable, the object of SomeOtherClass
that ref1
refers to does also. Once a group of objects becomes inaccessible, all of them might be collected during the next garbage-collection sweep. There is no guarantee in which order their Finalize()
methods will be called. So the object of SomeOtherClass
that ref1
refers to may have already been removed by the time SomeClass.Finalize()
is called! Setting ref1
to null
/Nothing
is redundant. But writing the Finalize()
method provides code for the garbage collector to execute, and obliges it to resurrect the object, thus unnecessarily slowing it down. Even worse, the memory is not reclaimed until the next garbage-collection sweep, further delaying the cleanup.
Do not write the Finalize()
method unless you have a good reason. It makes more work for the garbage collector. The main reason for implementing
Finalize()
is to make sure you free unmanaged resources, as you’ll see in Gotcha #36, "Releasing managed resources in Finalize() can wreak havoc." Only write it in the context of the Dispose design pattern (see Gotcha #40, "Implementing IDisposable isn’t enough“).
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," Gotcha #40, "Implementing IDisposable isn’t enough," and Gotcha #41, "Using the Dispose Design Pattern doesn’t guarantee cleanup."
Do not access managed resources within the Finalize()
method. Typically objects may have dependencies on other objects. In a language like C++, it is not unusual within a destructor to communicate with associated objects. However, when working with .NET, you should not carry those practices over to the Finalize()
method.
You may ask, “So what if I access other objects within Finalize()
?” The reason you shouldn’t is that the result cannot be predicted. You have no idea of the order in which the garbage collector will call the Finalize()
method of your objects. You may write code that accesses other objects within Finalize()
, run your program, and say, “See, it works!” The phrase “it works” is a very unpleasant one in programming. It’s like saying, “See, I drove the wrong direction on a one-way street and nothing went wrong.” In such cases, it is not a question of if things will go wrong, but when. This is illustrated in Example 5-2.
Example 5-2. Accessing managed resources
//SomeOtherClass.cs using System; namespace FinalizeAndManagedResources { public class SomeOtherClass { private int[] values = new int[1000000]; public void Notify() { Console.WriteLine("Notify called on SomeOtherClass"); } ~SomeOtherClass() { Console.WriteLine("*** SomeOtherClass Finalized"); } } } //SomeClass.cs using System; namespace FinalizeAndManagedResources { public class SomeClass { private SomeOtherClass other; public void Setother(SomeOtherClass otherObject) { other = otherObject; } ~SomeClass() { Console.WriteLine("Finalize called on SomeClass"); if(other != null) { other.Notify(); } } } } //Test.cs using System; namespace FinalizeAndManagedResources { class Test { [STAThread] static void Main(string[] args) { SomeClass object1 = new SomeClass(); SomeOtherClass object2 = new SomeOtherClass(); object1.Setother(object2); } } }
'SomeOtherClass.vb Public Class SomeOtherClass Private values() As Integer = New Integer(1000000 - 1) {} Public Sub Notify() Console.WriteLine("Notify called on SomeOtherClass") End Sub Protected Overrides Sub Finalize() MyBase.Finalize() Console.WriteLine("*** SomeOtherClass Finalized") End Sub End Class 'SomeClass.vb Public Class SomeClass Private other As SomeOtherClass Public Sub Setother(ByVal otherObject As SomeOtherClass) other = otherObject End Sub Protected Overrides Sub Finalize() MyBase.Finalize() Console.WriteLine("Finalize called on SomeClass") If Not other Is Nothing Then other.Notify() End If End Sub End Class 'Test.vb Module Test Sub Main() Dim object1 As SomeClass = New SomeClass Dim object2 As SomeOtherClass = New SomeOtherClass object1.Setother(object2) End Sub End Module
In this example, an instance of SomeClass
has a reference to an instance of SomeOtherClass
. When the object of SomeClass
is finalized, it calls the Notify()
method on the SomeOtherClass
object. In the Main()
method of the Test
module, you create instances of the two classes and relate them using the SetOther()
method of SomeClass
. When the program completes execution, both the objects are garbage collected. The question is, in which order?
Figure 5-2 shows one possible scenario. (One of my reviewers asked how many times I had to run the program to get this output. It took only one run!)
The Notify()
method is called on the object after its Finalize()
method has already executed. This is strange behavior and may lead to unpredictable results. Had it thrown an exception or crashed, it would be easy to realize the problem and fix it. Because it looks like it works, the problem remains obscured and unfixed.
Here’s another issue to consider. Suppose the method called during finalization is trying to be a good thread-safe citizen. Take a look at Example 5-3. Though this example is somewhat contrived, it starkly illustrates the risks of accessing managed resources in Finalize()
.
Example 5-3. Inadvertently locking from within Finalize()
//SomeOtherClass.cs using System; namespace FinalizeAndManagedResources { public class SomeOtherClass { private int[] values = new int[1000000]; public void Notify() { Console.WriteLine("Entering Notify"); lock(this) { Console.WriteLine("Notify called on SomeOtherClass"); } } ~SomeOtherClass() { Console.WriteLine("*** SomeOtherClass Finalized"); } } } //SomeClass.cs using System; namespace FinalizeAndManagedResources { public class SomeClass { private SomeOtherClass other; public void Setother(SomeOtherClass otherObject) { other = otherObject; } ~SomeClass() { Console.WriteLine("Finalize called on SomeClass"); if(other != null) { other.Notify(); } } } } //Test.cs using System; namespace FinalizeAndManagedResources { class Test { private static void ohoh(SomeOtherClass obj) { SomeClass object1 = new SomeClass(); object1.Setother(obj); } [STAThread] static void Main(string[] args) { SomeOtherClass object2 = new SomeOtherClass(); lock(object2) { ohoh(object2); GC.Collect(); //GC.WaitForPendingFinalizers(); Console.WriteLine("OK let's release lock in Main"); } Console.WriteLine("Are we here"); } } }
'SomeOtherClass.vb Public Class SomeOtherClass Private values() As Integer = New Integer(1000000 - 1) {} Public Sub Notify() Console.WriteLine("Entering Notify") SyncLock Me Console.WriteLine("Notify called on SomeOtherClass") End SyncLock End Sub Protected Overrides Sub Finalize() MyBase.Finalize() Console.WriteLine("*** SomeOtherClass Finalized") End Sub End Class 'SomeClass.vb Public Class SomeClass Private other As SomeOtherClass Public Sub Setother(ByVal otherObject As SomeOtherClass) other = otherObject End Sub Protected Overrides Sub Finalize() MyBase.Finalize() Console.WriteLine("Finalize called on SomeClass") If Not other Is Nothing Then other.Notify() End If End Sub End Class 'Test.vb Module Test Sub ohoh(ByVal obj As SomeOtherClass) Dim object1 As New SomeClass object1.Setother(obj) End Sub Sub Main() Dim object2 As New SomeOtherClass SyncLock object2 ohoh(object2) GC.Collect() 'GC.WaitForPendingFinalizers() Console.WriteLine("OK let's release lock in Main") End SyncLock Console.WriteLine("Are we here") End Sub End Module
Within the Main()
method you get a lock on an object of SomeOtherClass
. Then you create an instance of SomeClass
and the instance of SomeOtherClass
is associated with it. When GC.Collect()
is invoked, since the instance of SomeClass
is no longer accessible, its Finalize()
is called. Within this Finalize()
method, you invoke Notify()
on the instance of SomeOtherClass
. In Notify()
, you lock the object. However, the main thread within the Main()
method already owns a lock on the object. This blocks the garbage collector thread—it cannot continue until the Main()
method completes. (Main()
is able to run to completion because the call to GC.Collect()
does not block, even though the CLR’s finalization thread does.) You can see this in the output from the program, shown in Figure 5-3.
Let’s take this a bit further. The GC
class’s WaitForPendingFinalizers()
method suspends the calling thread until the finalization thread has run the Finalize()
method on all eligible objects in its queue. Now what happens if you uncomment the GC.WaitForPendingFinalizers()
in the Main()
method in Example 5-3? The output is shown in Figure 5-4.
The program is deadlocked. You may say, well, don’t call GC.WaitForPendingFinalizers()
, or GC.Collect()
for that matter. But what if you had called some other method on the object from another thread, and that thread holds a lock on the object while it waits for something else? There is a high probability of deadlock occurring if you access managed resources within the Finalize()
method.
Do not touch any managed objects within the Finalize()
method. Do not call any methods on other objects, or even set their references to null
/Nothing
. This could lead to unpredictable results, including the possibility of crash or deadlock. Only release unmanaged resources within Finalize()
.
Gotcha #35, "Writing Finalize() is rarely a good idea," 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," Gotcha #40, "Implementing IDisposable isn’t enough," and Gotcha #41, "Using the Dispose Design Pattern doesn’t guarantee cleanup."
Object.Finalize()
is an odd method. It is defined as protected overridable
. As mentioned in the introduction to this chapter, in C# you implement your class’s Finalize()
method with the special syntax ~
NameOfClass
, where NameOfClass
is the actual name of your class. This pseudo-destructor is compiled into the Finalize()
method during the MSIL translation.
Furthermore, within the C# destructor, you should not attempt to call Finalize()
on the base class. The compiler-generated Finalize()
takes care of calling the base class’s Finalize()
, even if an exception is raised, because the call is safe inside a finally
block.
For example, look at this pseudo-destructor:
~MyClass() { // Whatever cleanup code you write }
The C# compiler will translate this into the Finalize()
method in your assembly. A look at the generated MSIL using ildasm.exe will clarify this for you (see Figure 5-5).
If a C# programmer ever has to work with VB.NET code, he should remember that the rules are different. In VB.NET, you write Finalize()
as a Protected
Overrides
method, and you must call MyBase.Finalize()
, as shown in Example 5-4.
Example 5-4. Writing Finalize() in VB.NET
Protected Overrides Sub Finalize() Try 'Whatever cleanup code you write Finally MyBase.Finalize() End Try End Sub
However, if you use the Dispose design pattern (refer to Gotcha #40, "Implementing IDisposable isn’t enough“), then you most likely will not implement the Finalize()
method in your derived classes.
In C#, do not invoke the base class’s Finalize()
method from within the destructor. In VB.NET, do invoke the base class’s Finalize()
method from within the Finalize()
method.
Gotcha #35, "Writing Finalize() is rarely a good idea," Gotcha #36, "Releasing managed resources in Finalize() can wreak havoc," Gotcha #38, "Depending on Finalize() can tie up critical resources," Gotcha #39, "Using Finalize() on disposed objects is costly," Gotcha #40, "Implementing IDisposable isn’t enough," and Gotcha #41, "Using the Dispose Design Pattern doesn’t guarantee cleanup."
When an object becomes inaccessible it may remain in that state for an unpredictable amount of time. The garbage collector decides when the memory needs to be reclaimed based on its own heuristics, which include factors such as the amount of memory being used. The problem is that the time between when you have quit using the object and when Finalize()
is called on it might be very long. If your object is using some critical unmanaged resources, you will be holding onto them in the meantime. This may lead to resource contention that is not detected or handled well within your .NET program.
Consider the case where a .NET object communicates with a COM component running in a different process. The .NET object is very small (say only a few bytes in size). The COM object it communicates with, however, is large. Now, say you have a loop where you create one .NET object per iteration, using it and letting go of the reference. When will the unmanaged resource be released?
The answer, based on what you’ve read so far in this chapter, is eventually-- but who knows exactly when?
Fortunately, .NET provides a mechanism to support synchronous cleanup of resources used by objects. If an object implements the IDisposable
interface, it lets its users know that it offers a Dispose()
method they can call to release the object. (Dispose()
is the only method defined by IDisposable
.) Because the users call Dispose()
while the object is still accessible, it can free both managed and unmanaged resources.
An illustration of this is shown in Example 5-5. The code defines the Wrapper
class, and states that it implements IDisposable
. The Dispose()
method cleans up the COM object by calling the ReleaseComObject()
method of the System.Runtime.InteropServices.Marshal
class. As a fail-safe mechanism to make sure the COM object gets released, the Finalize()
method does the same thing. (ReleaseComObject()
is discussed in Gotcha #65, “Release of COM object is confusing.”)
Example 5-5. Using unmanaged resources from .NET
//Wrapper.cs using System; using ACOMCompLib; namespace FinalizePeril { public class Wrapper : IDisposable { IMyComp comp = new MyCompClass(); public int doSomething() { int result; comp.doSomething(out result); return result; } ~Wrapper() { System.Runtime.InteropServices.Marshal.ReleaseComObject(comp); } #region IDisposable Members public void Dispose() { System.Runtime.InteropServices.Marshal.ReleaseComObject(comp); } #endregion } } //Test.cs using System; using ACOMCompLib; namespace FinalizePeril { class Test { [STAThread] static void Main(string[] args) { int iterations = Convert.ToInt32(args[0]); int result = 0; for(int i = 0; i < iterations; i++) { Wrapper theWrapper = null; try { theWrapper = new Wrapper(); result = theWrapper.doSomething(); } finally { theWrapper.Dispose(); } } Console.WriteLine(result); Console.WriteLine("End of Main"); } } }
'Wrapper.vb Imports ACOMCompLib Public Class Wrapper Implements IDisposable Dim comp As IMyComp = New MyCompClass Public Function doSomething() As Integer Dim result As Integer comp.doSomething(result) Return result End Function Public Sub Dispose() Implements System.IDisposable.Dispose System.Runtime.InteropServices.Marshal.ReleaseComObject(comp) End Sub Protected Overrides Sub Finalize() MyBase.Finalize() System.Runtime.InteropServices.Marshal.ReleaseComObject(comp) End Sub End Class 'Test.vb Module Test Sub Main(ByVal args() As String) Dim iterations As Integer = Convert.ToInt32(args(0)) Dim result As Integer = 0 For i As Integer = 0 To iterations - 1 Dim theWrapper As Wrapper Try theWrapper = New Wrapper result = theWrapper.doSomething() Finally theWrapper.Dispose() End Try Console.WriteLine(result) Console.WriteLine("End of Main") Next End Sub End Module
In the example, one instance of Wrapper
is created for each pass through the loop. Each object of Wrapper
has a reference to a COM component. What would happen if you didn’t call theWrapper.Dispose()
? Well, if the COM component is in-process, you don’t have much to worry about. As the memory usage increases, the garbage collector will kick in and Finalize()
the inaccessible objects. However, if the COM component is out-of-process, then you are simply out of luck, especially if the COM components are large. As far as the CLR is concerned it will have no reason to garbage-collect because the memory utilization on its side is pretty low. As a result, it will not call Finalize()
.
A good analogy is to consider having two small tables of the same size. On one table you place very small objects, say peanuts. For each peanut you place on the first table, you place a larger object on the second table, say a watermelon. The second table will fill up pretty quickly, while the first table has a lot of free space. This is exactly what happens in the example above if you neglect to call theWrapper.Dispose()
. The Wrapper and the Runtime Callable Wrapper (RCW) that it uses are very small objects and thousands of these can be held in memory. However, if the related COM objects are watermelon-sized, you get into memory contention on the COM component server side. By calling theWrapper.Dispose()
, you make sure the COM objects get released each time through the loop.
You can’t predict exactly when Finalize()
will be called on an object; thus, any resource not released by the programmer will be held until that happens. This could cause some undesirable side effects. By implementing the IDisposable
interface and its Dispose()
method, you give users of your object a way to clean it up synchronously.
As I pointed out earlier, the Finalize()
and Dispose()
methods of the Wrapper
class both call ReleaseComObject()
. Of course, you don’t want to repeat the code in both. (Refer to the Don’t Repeat Yourself (DRY) principle in [Hunt00].) You’ll see how to refactor these in Gotcha #40, "Implementing IDisposable isn’t enough."
Do not rely on the CLR to call the Finalize()
method. Allow users of your object to properly clean up by implementing the System.IDisposable
interface. This way, they can call the Dispose()
method on the object when they are done using it.
Gotcha #35, "Writing Finalize() is rarely a good idea," Gotcha #36, "Releasing managed resources in Finalize() can wreak havoc," Gotcha #39, "Using Finalize() on disposed objects is costly," Gotcha #40, "Implementing IDisposable isn’t enough," Gotcha #41, "Using the Dispose Design Pattern doesn’t guarantee cleanup," and Gotcha #65, “Release of COM object is confusing.”
It is better to Dispose()
an object than to depend on its Finalize()
being called, because Dispose()
lets you control the cleanup of an object (see Gotcha #38, "Depending on Finalize() can tie up critical resources“). Also, there is overhead if the garbage collector needs to call Finalize()
(see Gotcha #39, "Using Finalize() on disposed objects is costly“). If an object has been properly disposed of (by the call to Dispose()
), there is no need for its Finalize()
to be called. After all, Dispose()
should have already taken care of its resources, both managed and unmanaged. What would Finalize()
need to do in this case?
.NET provides a mechanism (GC.SuppressFinalize()
), which tells the CLR not to invoke Finalize()
on an object. Calling this method eliminates the overhead of the Finalize()
call and the accompanying delay in reclaiming memory.
Let’s explore the usage of GC.SuppressFinalize()
by studying Example 5-6.
Example 5-6. Suppressing the Finalize() call
using System; namespace SuppressFinalize { public class Test : IDisposable { private readonly int id; public Test(int theID) { id = theID; } ~Test() { Console.WriteLine("Finalize called on {0}", id); } #region IDisposable Members public void Dispose() { Console.WriteLine("Dispose called on {0}", id); GC.SuppressFinalize(this); } #endregion [STAThread] static void Main(string[] args) { int count = 1; Test object1 = new Test(count++); Test object2 = new Test(count++); object1.Dispose(); Console.WriteLine("Main done"); } } }
Public Class Test Implements IDisposable Private ReadOnly id As Integer Public Sub New(ByVal theID As Integer) id = theID End Sub Protected Overrides Sub Finalize() MyBase.Finalize() Console.WriteLine("Finalize called on {0}", id) End Sub Public Sub Dispose() Implements System.IDisposable.Dispose Console.WriteLine("Dispose called on {0}", id) GC.SuppressFinalize(Me) End Sub Public Shared Sub Main() Dim count As Integer = 1 Dim object1 As New Test(count) count += 1 Dim object2 As New Test(count) object1.Dispose() Console.WriteLine("Main done") End Sub End Class
In this example, the class Test
implements the IDisposable
interface. Its Dispose()
method invokes GC.SuppressFinalize()
. In Main()
, you create two objects of Test
and dispose of only one of them. When you execute the code, you get the output shown in Figure 5-6.
Note that Finalize()
gets called on the second object, but not on the first. When you invoke Dispose()
on the first object, you indicate to the CLR that it should not bother invoking Finalize()
on this object. As a result, the memory used by the object gets released without the added delay involved with Finalize()
.
Calling Finalize()
on an object involves overhead. This should be avoided as much as possible. From within your Dispose()
method, call GC.SuppressFinalize()
.
Gotcha #35, "Writing Finalize() is rarely a good idea," Gotcha #36, "Releasing managed resources in Finalize() can wreak havoc," Gotcha #38, "Depending on Finalize() can tie up critical resources," Gotcha #40, "Implementing IDisposable isn’t enough," and Gotcha #41, "Using the Dispose Design Pattern doesn’t guarantee cleanup."
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#)
//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)
'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.
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
//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 } }
'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
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.
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."
While the IDisposable
interface and the Dispose Design Pattern are very important to follow, these are recommendations on how you write a class, not how you use an instance of the class. If you create an object, the CLR may eventually call its Finalize()
. On the other hand, Dispose()
is never called automatically. Users of your object have the responsibility of calling it. What if they forget to?
The problem does not end there. Even if you have a call to Dispose()
, what if an exception is thrown before the program reaches that statement? What can you do to ensure that Dispose()
is called and thereby increase the probability of proper cleanup?
To deal with these issues, C# offers the using
keyword (though there is no way to force the user to use it). Consider Example 5-10.
In this example, you use the using
keyword when creating object1
of the Derived
class. You do not call its Dispose()
method. (The class Derived
is the same as the one used in Example 5-7.) The using
keyword provides a significant benefit. It not only makes the call to Dispose()
, it does so in a finally
block, as the generated MSIL in Figure 5-8 shows. This ensures that the object is disposed of properly even when an exception is thrown.
You can declare more than one reference in the using
declaration:
using(Derived obj1 = new Derived(1), obj2 = new Derived(2))
The Dispose()
method, in this case, will be called on each referenced object at the appropriate time.
Unfortunately, using
is only available in C#. In VB.NET, to make sure Dispose()
is invoked, call it in a Finally
block. A VB.NET example of calling Dispose()
in the Finally
block is shown below:
Try theWrapper = New Wrapper result = theWrapper.doSomething() Finally theWrapper.Dispose() End Try
How does this differ in .NET 2.0 Beta 1? A Using
...End Using
block is being introduced in the next version of VB.NET.
The using
keyword in C# improves your chances of cleaning up your resources properly. In VB.NET, you have to take responsibility for invoking Dispose()
by calling it within a Finally
block. Take advantage of the Using
...End Using
block when you move to .NET 2.0.
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 #40, "Implementing IDisposable isn’t enough."
3.145.81.173