当你的实现是一个空方法时,如何“正确” 实现Dispose()(根据FxCop)?(CA1063)

16

我有一个接口的实现,该接口扩展了IDisposable。在我的特定接口实现中,我不需要处理任何内容,所以我只是创建了一个空的Dispose()方法。

public interface IMyStuff : IDisposable
{
}

public MyStuffImpl : IMyStuff
{
    public void Dispose()
    {
    }
}

现在在FxCop中,这会导致CA1063警告:

Error, Certainty 95, for ImplementIDisposableCorrectly
{
    Resolution   : "Provide an overridable implementation of Dispose(
                   bool) on 'MyStuffImpl' or mark the type as sealed. 
                   A call to Dispose(false) should only clean up native 
                   resources. A call to Dispose(true) should clean up 
                   both managed and native resources."
}
CriticalWarning, Certainty 75, for CallGCSuppressFinalizeCorrectly
{
    Resolution   : "Change 'MyStuffImpl.Dispose()' to call 'GC.SuppressFinalize(
                   object)'. This will prevent derived types that introduce 
                   a finalizer from needing to re-implement 'IDisposable' 
                   to call it."
}
Error, Certainty 95, for ImplementIDisposableCorrectly
{
    Resolution   : "Modify 'MyStuffImpl.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."
}

看起来我可以通过以下两种方式解决这个问题:


将该类设为sealed

public sealed MyStuffImpl : IMyStuff
{
    public void Dispose()
    {
    }
}

实现典型模式的一部分:

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

    private void Dispose(bool disposing)
    {
    }
}

就我的情况而言,我并不打算扩展此实现,所以我可能会通过将其密封(sealed)来解决问题,但我承认我不是很明白它是否被密封重要。

另外,仅仅因为我的类被密封了,FxCop不再告诉我Dispose()应该调用GC.SupressFinalize(this); ,但这真的是事实吗?在.NET中无论何时都调用SupressFinalize是更好的做法吗?


如果您的接口有一些不需要处理的实现,那么它可能不应该实现IDisposable。根据需要,您可以另外实现IDisposable以及您的接口。 - Daniel Mann
@DBM OP正在实现另一个继承IDisposable接口的接口。IEnumerator<T>就是一个例子。 - phoog
1
@DBM:如果大多数实现都是一次性的,那么这个接口也应该是一次性的,以鼓励接口的用户正确地处理。 - SLaks
我不知道 FxCop 在做什么,但我想指出你的类实际上缺少终结器。因此 SuppressFinalize 没有任何作用。 - dowhilefor
@DBM:如果一个工厂要返回可能是或不是 IDisposable 的东西,那么工厂的返回类型应该是 IDisposable。 这就是为什么 IEnumerator<T> 实现 IDisposable 的原因——它是一个工厂方法的返回类型。 - supercat
是的,虽然这并不重要,但在我的实际应用程序中,该实例是依赖注入的,并且某些实现确实需要进行处理。 - CodingWithSpike
2个回答

12

SuppressFinalize()方法只有在实例拥有终结器时才有意义。
如果您的类没有终结器,但未被sealed修饰,您仍应该调用SuppressFinalize()以防止后续派生的类添加了终结器。

您提出的两种方案都是正确的,除了Dispose(bool)需要变为protected virtual之外。


1
实际上,第二个选项是不正确的,因为 Dispose(bool) 重载不能被覆盖。感谢指出在没有终结器的对象上使用 SuppressFinalize 是毫无意义的。 - phoog
有没有这样一种情况,即一个类应该向不为之设计的基类添加清理终结器,并且是否有任何原因使这样的类无法在其自己的Dispose方法中处理GC.SuppressFinalize?我知道对于仅目的是记录释放失败的类具有终结器可能有用,但当基类未经设计以进行清理时,在终结器中尝试清理似乎是危险和错误的。封装可终结类的实例似乎更安全。 - supercat
@supercat:如果派生类添加了非托管资源,则无法覆盖Dispose()方法。(虽然我不确定为什么它不能在Dispose(true)中调用SupressFinalize()方法) - SLaks
1
@SLaks:派生类何时应添加非托管资源,而不是封装这些资源在一个新类中以管理它们?那个新类的实例将成为一个托管资源,因此无需将非托管资源添加到派生类中。 - supercat

2
在您的“实现典型模式的一部分”选项中,您应该将Dispose(bool)方法设置为受保护的虚拟
protected virtual void Dispose(bool disposing) 
{ 
} 

这将为子类提供处理所管理的任何资源的释放的机会。这就是“可重写(overridable)”在“提供Dispose(bool)的可重写实现”中的意思。

当然,public virtual也可以满足FxCop。


@rally25rs,但是FxCop在警告你时已经提到了这一点,它说“提供一个可重写的Dispose(bool)实现”。如果你提供了一个不可重写的Dispose(bool)实现,那么警告是否消失了呢?如果是这样,那就很奇怪了。 - phoog
是的,在我的第二个例子中,Dispose() 是私有的,没有 FxCop 警告。 - CodingWithSpike

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接