views:

593

answers:

5

I have an abstract class that implements IDisposable, like so:

public abstract class ConnectionAccessor : IDisposable
{
    public abstract void Dispose();
}

In Visual Studio 2008 Team System, I ran Code Analysis on my project and one of the warnings that came up was the following:

Microsoft.Design : Modify 'ConnectionAccessor.Dispose()' so that it calls Dispose(true), then calls GC.SuppressFinalize on the current object instance ('this' or 'Me' in Visual Basic), and then returns.

Is it just being silly, telling me to modify the body of an abstract method, or should I do something further in any derived instance of Dispose?

+6  A: 

The warning basically tells you to implement the Dispose pattern in your class.

The resulting code should look like this:

public abstract class ConnectionAccessor : IDisposable
{
    ~ConnectionAccessor()
    {
        Dispose(false);
    }

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

    protected virtual void Dispose(bool disposing)
    {
    }
}
dtb
+1 for mentioning the pattern, and pointing out that the Dispose(bool) should be the virtual/abstract method that derived classes should implement or override, not Dispose().
Yoopergeek
+3  A: 

You should follow the conventional pattern for implementing Dispose. Making Dispose() virtual is considered bad practice, because the conventional pattern emphasizes reuse of code in "managed cleanup" (API client calling Dispose() directly or via using) and "unmanaged cleanup" (GC calling finalizer). To remind, the pattern is this:

public class Base
{
    ~Base()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // so that Dispose(false) isn't called later
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
             // Dispose all owned managed objects
        }

        // Release unmanaged resources
    }
}

Key here is that there's no duplication between finalizer and Dispose for unmanaged cleanup, and yet any derived class can extend both managed and unmanaged cleanup.

For your case, what you should do is this:

protected abstract void Dispose(bool disposing)

and leave everything else as is. Even that is of dubious value, since you're enforcing your derived classes to implement Dispose now - and how do you know that all of them need it? If your base class has nothing to dispose, but most derived classes likely do (with a few exceptions, perhaps), then just provide an empty implementation. It is what System.IO.Stream (itself abstract) does, so there is precedent.

Pavel Minaev
romkyns
A: 

The warning is interesting though. Eric Lippert, one of the C# designers, blogged about why error messages should be "Diagnostic but not prescriptive: describe the problem, not the solution". Read here.

bja
It's not a compiler error message. It's the result of running a tool that specifically points out problematic things. There's nothing wrong with it also pointing out the solution.
Pavel Minaev
Sure. Just wanted to share the link. It came to me because the message doesn't state the problem but gives the solution only. Hopefully there was a previous message doing that.
bja
This is kind of tangential to the original question.
Michael Donohue
+1  A: 

While it does seem a little like nit-picking, the advice is valid. You are already indicating that you expect any sub-types of ConnectionAccessor will have something that they need to dispose. Therefore, it does seem better to ensure that the proper cleanup is done (in terms of the GC.SuppressFinalize call) by the base class rather than relying on each subtype to do it.

I use the dispose pattern mentioned in Bruce Wagners book Effective C# which is basically:

public class BaseClass : IDisposable
{
    private bool _disposed = false;
    ~BaseClass()
    {
        Dispose(false);
    }

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

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
            //release managed resources
        }

        //release unmanaged resources

        _disposed = true;
    }
}

public void Derived : BaseClass
{
    private bool _disposed = false;

    protected override void Dispose(bool disposing)
    {
        if (_disposed) 
            return;

        if (disposing)
        {
            //release managed resources
        }

        //release unmanaged resources

        base.Dispose(disposing);
        _disposed = true;
    }
akmad
+3  A: 

The only gripe I'd have with the answers provided so far is that they all assume that you need to have a finalizer, which isn't necessarily the case. There is a fairly significant performance overhead associated with finalization, which I wouldn't want to impose on all of my derived classes if it wasn't necessary.

See this blog post by Joe Duffy, which explains when you may or may not need a finalizer, and how to properly implement the Dispose pattern in either case.
Summarizing Joe's blog post, unless you're doing something fairly low-level dealing with unmanaged memory, you shouldn't implement a finalizer. As a general rule of thumb, if your class only holds references to managed types that implement IDisposable themselves, you don't need the finalizer (but should implement IDisposable and dispose of those resources). If you are allocating unmanaged resources directly from your code (PInvoke?) and those resources must be freed, you need one. A derived class can always add a finalizer if it really needs it, but forcing all derived classes to have a finalizer by putting it in the base class causes all derived classes to be impacted by the performance hit of finalizable objects when that overhead may not be necessary.

Doug Rohrer
My favourite gripe. Most people just don't bother trying to understand this, and the best advice they tend to get is to just implement the Microsoft pattern. Here's what I think is much better advice than the "official" pattern: http://nitoprograms.blogspot.com/2009/08/how-to-implement-idisposable-and.html
romkyns
That is indeed a good blog post on the subject - simple and too the point.
Doug Rohrer