An IDisposable Enforcement Trick

Posted: September 12, 2009 in .NET, Architecture, C#, Performance, Quality
Tags: , , , ,

For ideas on when to implement IDisposable, see another of my blog posts, Implement IDisposable More.

If you decide that your class should implement IDisposable, you have done your part to be sure that resources can be cleaned up immediately with a call you your Dispose method.

However, what if another object fails to call your Dispose method? Your Dispose method will never run.

In most cases, if a Dispose method does not run, I consider it a coding error. To catch it, I developed this simple enforcement trick:

public class SomeDisposableClass: IDisposable
{
bool isDisposed;
StreamReader myStreamReader;

public SomeDisposableClass()
{
myStreamReader = new StreamReader(“c:\foo.txt”);
}

public void Dispose()
{
isDisposed = true;

if (myStreamReader != null)
myStreamReader.Dispose();
}

#if DEBUG
~SomeDisposableClass()
{
if (!isDisposed)
Console.WriteLine(“An instance of SomeDisposableClass was not disposed before garbage collection.”);
}
#endif
}

A private field called isDisposed defaults to false, but is set to true if the Dispose method runs. If the Dispose method does not run, a finalizer catches the coding error by outputting a message to the console window.

Objects with finalizers pay a garbage collection performance penalty. To avoid that penalty, the above code only compiles in the finalizer in debug mode.

Typically, you can spot these error messages in the console window when garbage collection runs at application shut-down. However, they can show up at any time if your object is garbage collected sooner.

If you really want to enforce that your Dispose methods get called, throw an exception instead of outputting a message to the console window.

Be careful what you do in your finalizer. The finalizer will run on a garbage-collection thread, so avoid the temptation to reference any non-thread-safe fields. While it would be nice to pop up a message box to report not-disposed errors, during application shutdown, you may or may not catch a glimpse of it before shutdown completes.

Please add comments to refine the use of IDisposable or to show support for this enforcement trick.

Enjoy,

Dale

Technorati Tags: ,
Comments
  1. Martin Fassbach says:

    When reading “trick” and “enforcement” I thought, there’d be a solution, which leaves even the most evil user of your code with no other chance but to call the dispose method.

    I designed such a solution below, although it is unhandy to use for friendly users or oneself.
    I replaced obvious parts like the destructor and the dispose method with “…”.

    public class TrickDisposable : IDisposable
    {
    public static performOperation(TrickFunctor functor)
    {
    functor.preOperation();
    using (TrickDisposable dispose = new …)
    {
    functor.mainOperation(dispose);
    }
    functor.postOperation();
    }
    private TrickDisposable();

    }

    interface TrickFunctor
    {
    preOperation();
    mainOperation(TrickDisposable dispose);
    postOperation();
    }

    • Martin Fassbach says:

      I forgot to explain the concept and flaws of my solution:

      The trick is that the user can’t create an object of type TrickDisposable due to its private constructor. Alternatively the user implements the interface TrickFunctor and splits the code into three parts:
      – Preparations before using TrickDisposable
      – Operations during the lifecycle of a TrickDisposable
      object
      – Finishing performed after TrickDisposable isn’t needed any longer

      The most stupid user can’t do more harm than NOT splitting the code and thus creating an instance of TrickDisposable, which will be disposed when the method TrickFunctor.mainOperation() exits.

      The most evil user may put the main loop of the program into mainOperation() or even use multiple threads, each going to sleep forever after collecting an instance of TrickDisposable to circumvent the protection of my solution.

      Other drawbacks, despite the above leaks in safety, are its unhandiness especially when:
      – multiple instances of TrickDisposable are needed
      – a highly dynamical (in terms of ill-defined life-cycle) instance of TrickDisposable is required

  2. barnarddale says:

    One way to beef up the enforcement logic of my sample code would be to throw an exception in the finalizer rather than writing to the console. In this way, your application will stop at the first failed-to-dispose error and you won’t see any additional ones. Seeing an exception every time you exit an application may annoy you into adding calls to Dispose.

    In a large team environment, I found that most developers responded negatively to the throw-exception approach, and they ignored console messages completely. I think that to *really* enforce calls to Dispose, a team needs *strong technical leadership* combined with some Dispose enforcement pattern as I described above.

  3. Chris says:

    How about Debug.Fail("Diposable object not disposed!")? Seems less intrusive than throwing exception.

    • Dale Barnard says:

      I haven’t tried Debug.Fail. In reading the MSDN on that method, it looks like it pops up a messagebox, which may not work in finalizers. I have found that finalizers can’t be stopped to wait for user input because the .net engine will override such attempts. Have you tried Debug.Fail to see if it works? Let me know if you do–it would indeed be less intrusive. Thanks for the comment!

      • Chris says:

        @Dale:

        Just tried Debug.Fail using .NET 4 Client Profile. The error dialog box did pop up. Here’s what I found:

        . Debug.Fail will properly block the finalizer thread, while the main thread will continue as normal. This means if nobody dismisses the dialog box, nothing will be finalized from then on.
        . The finalizer thread will never prevent the program from exiting. So when the main thread has finished, the finalizer thread will be killed, bringing down the finalizer thread.

        This behavior seems quite fitting for purpose — the program executes as normal and we have an in-the-face error box that is more attention catching than stdout.

Leave a reply to barnarddale Cancel reply