2009-08-27

The First Rule of Implementing IDisposable and Finalizers

This post is part of How to Implement IDisposable and Finalizers: 3 Easy Rules.

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

IDisposable is not a destructor. Remember that .NET has a garbage collector that works just fine without requiring you to set member variables to null.

There are only two situations when IDisposable does need to be implemented; apply these tests to a class to determine if IDisposable is needed:

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

Note that only classes that own resources should free them. In particular, a class may have a reference to a shared resource; in this case, it should not free the resource because other classes may still be using it.

Here's a code example similar to what many beginner C# programmers write:

// This is an example of an incorrect IDisposable implementation.
public sealed class ErrorList : IDisposable
{
    private string category;
    private List<string> errors;

    public ErrorList(string category)
    {
        this.category = category;
        this.errors = new List<string>();
    }

    // (other methods go here to add/display error messages)

    // Completely unnecessary...
    public void Dispose()
    {
        if (this.errors != null)
        {
            this.errors.Clear();
            this.errors = null;
        }
    }
}

Some programmers (especially with C++ backgrounds) even go a step further and add a finalizer:

// This is an example of an incorrect and buggy IDisposable implementation.
public sealed class ErrorList : IDisposable
{
    private string category;
    private List<string> errors;

    public ErrorList(string category)
    {
        this.category = category;
        this.errors = new List<string>();
    }

    // (other methods go here to add/display error messages)

    // Completely unnecessary...
    public void Dispose()
    {
        if (this.errors != null)
        {
            this.errors.Clear();
            this.errors = null;
        }
    }

    ~ErrorList()
    {
        // Very bad!
        // This can cause an exception in the finalizer thread, crashing the application!
        this.Dispose();
    }
}

The correct implementation of IDisposable for this type is here:

// This is an example of a correct IDisposable implementation.
public sealed class ErrorList
{
    private string category;
    private List<string> errors;

    public ErrorList(string category)
    {
        this.category = category;
        this.errors = new List<string>();
    }
}

That's right, folks. The correct IDisposable implementation for this class is to not implement IDisposable! When an ErrorList instance becomes unreachable, the garbage collector will automatically reclaim all of its memory and resources.

Remember the two tests to determine if IDisposable is needed (owning unmanaged resources and owning managed resources). A simple checklist can be done as follows:

  1. Does the ErrorList class own unmanaged resources? No, it does not.
  2. Does the ErrorList class own managed resources? Remember, "managed resources" are any classes implementing IDisposable. So, check each owned member type:
    1. Does string implement IDisposable? No, it does not.
    2. Does List<string> implement IDisposable? No, it does not.
    3. Since none of the owned members implement IDisposable, the ErrorList class does not own any managed resources.
  3. Since there are no unmanaged resources and no managed resources owned by ErrorList, it does not need to implement IDisposable.

This post is part of How to Implement IDisposable and Finalizers: 3 Easy Rules.

5 comments:

  1. Excellent article...you should back up your assertion from the same conclusion stated by the MSDN docs: Implementing a Dispose Method.

    ReplyDelete
  2. Oh...I would add one other rule:

    Implement IDisposable if you have (managed) events which need to be unsubscribed too. Otherwise the managed object will become pinned in memory due to the subscribed event(s).

    ReplyDelete
  3. There isn't a clear consensus at this point regarding event subscriptions. There are three different approaches that I've seen:
    1) The class providing the event treats its event subscriptions as managed resources, unsubscribing all of its subscribers when it is disposed. [This is William's recommendation above].
    2) The class receiving the event treats its event subscriptions as managed resources, unsubscribing from its providers when it is disposed. This pattern is commonly found in MVVM projects, and is a little trickier to implement.
    3) The event subscription itself is considered a managed resource, initially owned by whatever component created the event subscription. This more closely follows the pattern used by the Rx framework.

    ReplyDelete
  4. What if in your example the list holds thousands of custom types. Say ErrorList class goes out of scope. But because GC may not start reclaiming memory, there are chances of memory leak.
    How to handle such scenario?
    Can we not write dispose to clear the list when ErrorList scope is over.

    ReplyDelete
  5. @Anonymous: as long as the custom types are not disposable, then there is no benefit to implementing IDisposable and clearing the list in Dispose.

    There is no chance of a memory leak; the garbage collector will work just fine.

    ReplyDelete