Wednesday, October 10, 2012

The Dispose() method and the inheritance

Today I am going to write about the IDisposable interface – it’s Dispose() method, and how this should be really implemented in derived classes. The simplest implementation of Dispose() method will be just introduction of the following code to the class, and that’s all:
public class A : IDisposable
{    public void Dispose()
    {        // Disposal logic here 
    }
}
 
and this seems to be fine, but it is not.

Let’s assume for now that everything is fine, and we ship a library with the above code in it. Somebody, who is using the library, can easily use it in the following way:
public class B : A
{    public new void Dispose()
    {        //Class B specific dispose logic in here
        //And the following call is essential here

        base.Dispose();
    }
}

This seems to be fine as well, unless someone else, or even the owner of class B will use it in the following way:
using (A item = new B())
{    // some logic here
}

In this case, when exiting from the using block, the Dispose() method of type A will be called, which is sitting in the method table in memory as a separate record, and has nothing related to the Dispose() method of type B. And this will become a possible reason for a memory leak – any unmanaged resources introduced in class B will not be cleaned up !

One solution, of course will be to cast item to type B and call it’s Dispose() method explicitly, but that’s just complicating things, because this will need to be done in a finally block.

So let’s now see what I meant saying “it is not” in the beginning of the article, when was defining class A.

The thing is the the implementer of a public type must take the responsibility of problems like the one described by implementing types the way, so the users will not need to worry about the usage.

And the solution is actually simple – in every case, when introducing a type which implements the IDisposable interface, the Dispose() method must be marked either virtual (and the derived classes should override that method), or whole the class must be marked as sealed.

So by defining class A as sealed we will explicitly restrict its usage the way it was described – the incorrect usage. Now let’s see what will change, if we’ll mark the Dispose() method in class A as virtual and in class B override it.

Remember, this will force a single method record for a derived type to appear in the method table, so no matter how you’ll reference an instance of type B (either through type A, or any other type in the hierarchy) the Dispose method will call the Dispose method of type B, which, assumable (if implemented correct – as in the example above) will call the parent type’s Dispose() method and this will go by chain until the top of the hierarchy, making sure that any resource in whole the hierarchy got disposed.

Good luck with development !

No comments: