Be Kind to Your GC: Examining IDisposable and Finalize Patterns

Through my discussions with various developers, I have noticed a common theme when it comes to disposing objects. Most developers are confused, or at least unclear, on why and when to use dispose and finalize.

Let me start off with this: The purpose of dispose and finalize is the same… to improve the performance of the application and the host system. You don’t need to implement either. When the GC runs, unused managed memory will be reclaimed. When your process exits, the entire process’s virtual memory will be released. When the system re-boots, any open native handles will be closed. Dispose and finalize are two patterns that make it easier for your application to be a “good citizen,” releasing memory as soon as it is no longer needed and closing handles as soon as you are done with them.

Deconstructing The “Standard” Pattern

Here is the standard Dispose example provided in the MSDN (cleaned-up a little bit):

using System;
using System.ComponentModel;
 
public class DisposeExample {
    public class MyResource: IDisposable {
        private IntPtr handle;
        private Component component = new Component();
        private bool disposed = false;
 
        public MyResource(IntPtr handle) {
            this.handle = handle;
        }
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing) {
            if(this.disposed) return;
            if(disposing) {
                component.Dispose();
            }
            CloseHandle(handle);
            handle = IntPtr.Zero;
            disposed = true;
        }
 
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);
 
        ~MyResource() {
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

Now let’s unpack it and take a closer look…

    public class MyResource: IDisposable {
        private IntPtr handle;
        private Component component = new Component();
        private bool disposed = false;

In this example, “handle” is an address pointer to any native resource (event, thread, file, mutex, etc.), “component” is any managed object that itself is disposable and “disposed” is a private flag to indicate whether or not this instance has been disposed.

        public MyResource(IntPtr handle) {
            this.handle = handle;
        }

This constructor takes a native handle, and keeps a reference to it. CAUTION! Don’t do this in your own code. Since this object did not create the handle, it should not close it. The external code that created it may expect it to still be open after our object is gone. The reason MSDN has this is to simplify the example, and show that it applies to all native handles.

        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

This is the Dispose method that fulfills the IDisposable interface contract. In this example, there is no cleanup code in this method, it is extracted into another dispose method that can be called from here, the finalizer, inheriting classes, etc.

Notice the call to GC.SuppressFinalize(this);. This just tells the GC that the object has already been cleaned-up, and the finalizer does not need to run.

        protected virtual void Dispose(bool disposing) {
            if(this.disposed) return;
            if(disposing) {
                component.Dispose();
            }
            CloseHandle(handle);
            handle = IntPtr.Zero;
            disposed = true;
        }

This Dispose(bool) method does the actual cleanup. It is protected virtual so that this class can be inherited and still have its resources cleaned-up.

            if(this.disposed) return;

This gate condition is necessary for two reasons: 1) there is a destructor on this class and that destructor calls this cleanup method, and 2) This class can be inherited, and this cleanup method may be called multiple times from that child class.

            if(disposing) {
                component.Dispose();
            }

This is an example of cleaning up managed object members when this object is disposed. This condition is only necessary because this cleanup method is called from the destructor. This condition prevents an object disposed exception when the finalizer runs.

        ~MyResource() {
            Dispose(false);
        }

Here is the destructor (also called a finalizer). It calls the cleanup method that actually releases the native handle, and indicates that the object is not being “disposed”, it is being “finalized”.

So Why Do I Need a Finalizer?

If you were paying attention, you may have noticed that the finalizer above might never be executed. Yes, really. If this class is properly consumed, and dispose is called when the object is no longer used, then the finalizer should never run. Why write one? I’m glad you asked. You should create a finalizer as a failsafe, in case the consuming code does not call Dispose(). That’s about it! The Dispose() method allows the application to free up resources as soon as they are unused (instead of waiting for GC to run or for the application to exit), and the finalizer provides a backup (for native objects only), in case Dispose() is never called.

A Better Disposable/Finalize Pattern

Whew! That was pretty intense. After taking it apart, I have a proposal for a better example to explain Dispose and Finalize, that I think will make it more clear. Let me know what you think!

using System;
using System.ComponentModel;
 
public class DisposeExample {
    public class MyResource : IDisposable {
        private IntPtr handle;
        private Component component = new Component();
        private bool disposed = false;
 
        public MyResource() {
            // create or initialize any native resources
            // this could happen at any time during the life of the
            // object, not just in the constructor
            handle = CreateEvent(IntPtr.Zero, false, false, "MyEvent");
        }
        public void Dispose() {
            // Since Dispose is called explicitly by user code, we know that
            // this object is no longer needed. Because of this, we
            // should immediately release any resources that this object is 
            // using, so that they can be made available for other processes or threads.
            CleanupManagedResources();
            CleanupNativeResources();
            // since we are cleaning up native resources, tell the GC
            // that it does not need to put this object on the 
            // finalizer queue
            GC.SuppressFinalize(this);
        }
 
        protected virtual void CleanupManagedResources() {
            // Since this object can be inherited, this method could 
            // be called multiple times. Only dispose objects once.
            if (disposed) return;
            // Cleaning up managed resources normally consists of 
            // calling Dispose() on all members that inherit from 
            // IDisposable.
            component.Dispose();
 
            disposed = true;
        }
        protected virtual void CleanupNativeResources() {
            // Cleaning up native resources normally consists of
            // closing native handles (file, process, sercurity context, etc.)
            CloseHandle(handle);
            handle = IntPtr.Zero;
        }
 
        [System.Runtime.InteropServices.DllImport("kernel32")]
        private extern static IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);
 
        [System.Runtime.InteropServices.DllImport("kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);
 
        ~MyResource() {
            // The destructor should ONLY cleanup native resources
            // Because the finalizer runs after the GC, any managed
            // members of this object may already be collected and zeroed.
            CleanupNativeResources();
        }
    }
}

Wrap It Up

Hopefully this clears up some confusion around disposable and finalizable objects. What other questions do you have? What is still unclear? Ask me, and I will gladly fill in anything I have missed.

Leave a Reply