C#abstract Dispose方法

时间:2009-11-09 20:40:10

标签: c# abstract-class dispose static-analysis

我有一个实现IDisposable的抽象类,如下所示:

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

在Visual Studio 2008 Team System中,我对项目运行了代码分析,其中一个警告如下:

  

Microsoft.Design:修改'ConnectionAccessor.Dispose()'以便它调用Dispose(true),然后在当前对象实例上调用GC.SuppressFinalize(在Visual Basic中为'this'或'Me'),然后返回

它只是愚蠢,告诉我修改抽象方法的主体,还是应该在Dispose的任何派生实例中做进一步的事情?

5 个答案:

答案 0 :(得分:12)

您应该遵循传统模式来实施Dispose。使Dispose()虚拟被认为是不好的做法,因为传统模式强调在“托管清理”(API客户端直接调用Dispose()或通过using)和“非托管清理”(GC)中重用代码调用终结器)。提醒一下,模式是:

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
    }
}

这里的关键是终结器和Dispose之间没有重复的非托管清理,但是任何派生类都可以扩展托管和非托管清理。

对于您的情况,您应该做的是:

protected abstract void Dispose(bool disposing)

并保留其他所有内容。即使这具有可疑价值,因为您现在正在强制执行派生类以实现Dispose - 您怎么知道所有这些都需要它?如果您的基类没有任何可处置的东西,但是大多数派生类可能会执行(可能有一些例外),那么只需提供一个空实现。这是System.IO.Stream(本身抽象)的作用,因此有先例。

答案 1 :(得分:10)

警告基本上会告诉您在班级中实施Dispose pattern

生成的代码应如下所示:

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

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

    protected virtual void Dispose(bool disposing)
    {
    }
}

答案 2 :(得分:3)

到目前为止,我对答案提出的唯一抱怨是,他们都认为你需要才能拥有终结者,但情况并非如此。最终确定会产生相当大的性能开销,如果没有必要,我不想强​​加给所有派生类。

请参阅Joe Duffy撰写的this blog post,其中说明何时可能需要或不需要终结器,以及如何在任何一种情况下正确实施Dispose模式。
总结Joe的博客文章,除非你正在做一些相当低级别的处理非托管内存的事情,否则你不应该实现终结器。作为一般经验法则,如果您的类仅保存对自身实现IDisposable的托管类型的引用,则不需要终结器(但应实现IDisposable并处置这些资源)。如果直接从代码中分配非托管资源(PInvoke?)并且必须释放这些资源,则需要一个。派生类总是可以添加一个终结器,如果它真的需要它,但是通过将它放在基类中强制所有派生类都有一个终结器会导致所有派生类受到最终化对象的性能损失的影响,而这可能不是必要的。

答案 3 :(得分:1)

虽然看起来有点像挑选,但建议是有效的。您已经指出您希望ConnectionAccessor的任何子类型都具有他们需要处理的某些。因此,最好确保基类完成正确的清理(就GC.SuppressFinalize调用而言),而不是依赖于每个子类型来完成它。

我使用布鲁斯瓦格纳斯书Effective C#中提到的处理模式,基本上是:

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;
    }

答案 4 :(得分:-1)

但警告很有意思。 C#设计师之一Eric Lippert在博客中写道,为什么错误消息应该是“诊断但不是规定性的:描述问题,而不是解决方案”。 Read here.