2009-08-27

How to Implement IDisposable and Finalizers: 3 Easy Rules

Microsoft's documentation on IDisposable is needlessly confusing. It really boils down to three simple rules.

Rule 1: Don't do it (unless you need to).

There are only two situations when IDisposable does need to be implemented:

  • The class owns unmanaged resources.
  • The class owns managed (IDisposable) resources.

See The First Rule of Implementing IDisposable and Finalizers for more details.

Rule 2: For a class owning managed resources, implement IDisposable (but not a finalizer)

This implementation of IDisposable should only call Dispose for each owned resource. It should not have any other code: no "if" statements, no setting anything to null; just calls to Dispose or Close.

The class should not have a finalizer.

See The Second Rule of Implementing IDisposable and Finalizers for more details.

Rule 3: For a class owning a single unmanaged resource, implement both IDisposable and a finalizer

A class that owns a single unmanaged resource should not be responsible for anything else. It should only be responsible for closing that resource.

No class should be responsible for multiple unmanaged resources.

No class should be responsible for both managed and unmanaged resources.

This implementation of IDisposable should call an internal "CloseHandle" method and then end with a call to GC.SuppressFinalize(this).

The internal "CloseHandle" method should close the handle if it is a valid value, and then set the handle to an invalid value. This makes "CloseHandle" (and therefore Dispose) safe to call multiple times.

The finalizer for the class should just call "CloseHandle".

See The Third Rule of Implementing IDisposable and Finalizers for more details.

6 comments:

  1. Some of the most solid advice I've ever seen on IDisposable. I wish all the other bloggers stopped advertising the "official" pattern so much. It's overly complicated to cater for use cases that are a bad idea in the first place.

    Thanks for your insightful observations and the best article on the topic ever - http://www.codeproject.com/KB/dotnet/idisposable.aspx :)

    ReplyDelete
  2. why don't rumble to Mircosoft that they are wrong in thier Dispose pattern? :)

    ReplyDelete
  3. Do you have any thoughts on whether it would be ok/recommended to wrap the Dispose code in try-catch:

    public void Dispose() {
    try {
    managedResource.Dispose();
    }
    catch { }
    }

    ReplyDelete
  4. catch { } is called "swallowing exceptions". It is almost always, by the definition of exceptions, the wrong thing to do. If you catch an exception, it should be handled. The only case where it shouldn't is a case where exceptions can be thrown for things that aren't actually broken. (Thread.Sleep, for example, can throw a ThreadInterruptedException if someone calls thatThread.Interrupt(). But nothing's actually "wrong".) However, cases like that are not that common, and they're usually already handled by the code that needs to handle them. All you're really doing by swallowing exceptions is sweeping errors under the rug and making them that much harder to diagnose and fix.

    ReplyDelete
  5. What if it's a COM object with a .close method - replace CloseHandle with a simple com_object.close and then Marshal.ReleaseComObject(com_object)?

    ReplyDelete
  6. I have very little COM interop experience, but I believe this is correct:

    A "Close" method on a COM object is an actual method, not a generic resource cleanup method (i.e., it actually performs an action; one that you don't necessarily want done in every use case). ReleaseComObject can be called from Dispose if your COM object is owned by that object.

    COM objects are considered managed resources (since the GC will deref them eventually).

    I'm basing my assumptions on CBrumme's blog post, which may help you in your design: http://blogs.msdn.com/b/cbrumme/archive/2003/04/16/51355.aspx

    ReplyDelete