当从IDisposable派生并在基类中提供实现时,代码分析CA1063会触发。

39

我有一些代码会触发Code Analysis警告CA1063:

CA1063 : Microsoft.Design : 从“Functionality”实现的接口列表中删除IDisposable,并覆盖基类Dispose实现。

然而,我不确定该怎么修复这个警告。

简单来说,我有一个接口IFunctionality,它派生自IDisposable,类Functionality 实现了IFunctionality,但派生自类Reusable以便能够重用一些代码。类Reusable也派生自IDisposable

public class Reusable : IDisposable {

  ~Reusable() {
    Dispose(false);
  }

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

  protected virtual void Dispose(Boolean disposing) {
    // ...
  }

  public void DoSomething() {
    // ...
  }

}

public interface IFunctionality : IDisposable {

  void DoSomething();

  void DoSomethingElse();

}

public class Functionality : Reusable, IFunctionality {

  public void DoSomethingElse() {
    // ...
  }

#if WORK_AROUND_CA1063
  // Removes CA1063
  protected override void Dispose(Boolean disposing) {
    base.Dispose(disposing);
  }
#endif

}

我可以通过在Functionality上重写Dispose并调用基类Dispose来消除警告,尽管这样做不应该改变代码的语义。

所以,在这种情况下,IDisposable是否有我忽视的东西,还是只是CA1063在这个特定结构上误报了?

我知道我可以抑制CA1063,但这条规则很广泛,我不想错过此规则报告的实现IDisposable中的其他任何问题。


老问题了,但是CA1063的问题往往可以通过将类标记为“sealed”来解决。如果类不是“sealed”,我认为该类必须是“virtual”或/和“Dispose()”方法必须是“protected”。 - mortb
3个回答

42
由于规则本身存在一个小错误,所以这是一个误报。在尝试确定类是否重新实现IDisposable时(在确定存在可以覆盖的基类实现之后),它只查看类的接口是否包括IDisposable。不幸的是,出现在程序集元数据中的接口列表包括“展开”的接口列表,包括在原始C#代码中显式实现的任何接口继承的接口。这意味着FxCop看到了一个声明,该声明看起来像您的Functionality类的以下声明:
public class Functionality : Reusable, IFunctionality, IDisposable
{
    ...
}

基于这个元数据表示,ImplementIDisposableCorrectly规则应该更加智能地尝试确定类是否实际上正在重新实现IDisposable(例如,通过查找显式Dispose()实现,如果基类具有可重写的Dispose(bool))。然而,鉴于该规则并没有这样做,您最好的方法是抑制错误的阳性。
顺便说一下,我建议认真考虑使用SuppressMessageAttribute来抑制错误的阳性,而不是您当前的条件编译方法。例如:
[SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly",
    Justification = "False positive.  IDisposable is inherited via IFunctionality.  See https://dev59.com/f2ox5IYBdhLWcg3w3X9S for details.")]
public class Functionality : Reusable, IFunctionality
{
    ...
}

此外,您可能需要认真考虑摆脱终结器...

“摆脱终结器”的链接已经失效。我无法确定它最初是指向http://joeduffyblog.com/2005/12/27/never-write-a-finalizer-again-well-almost-never/还是http://joeduffyblog.com/2008/02/17/idisposable-finalization-and-concurrency/,或者其他一些我尚未发现的文章。你能否编辑你的回答来修复这个链接? - Chris Nielsen

4

你的“workaround”在这里是正确的模式,用于实现IDisposable的派生类。

但我认为你应该重新考虑IFunctionality:IDisposable的设计。成为可处理的对象真的是IFunctionality的关注点吗?我认为这个决定应该由实现类来决定。


IDisposableIFunctionality 合约的重要部分,被系统的其余部分使用。然而,Reusable 提供了 Dispose 的实现。因此,这是设计上的考虑。 - Martin Liversage
"IDisposable是IFunctionality合约的重要部分" - 仍然听起来像将实现混合到接口中。但示例过于抽象,无法判断。 - H H
重要的是,在关闭期间,IFunctionality 的消费者应该处理所提供的实例。因此,基本上 IFunctionality 有一个关闭实例的方法,我决定使用 IDisposable.Dispose 来实现这个目的。非常感谢任何关于设计此功能的替代方法的反馈,但我想这应该在另一个问题中讨论。 - Martin Liversage
如果一个接口包含一个工厂方法,可能会产生需要由接收方处理的对象,则该方法的返回类型应实现 IDisposable 接口。最典型的例子是 IEnumerator<T>,它是由 IEnumerator<T>.GetEnumerator() 返回的。非泛型的 IEnumerator 也应该实现 IDisposable,原因与泛型版本相同,但 .NET 1.0 团队直到为时已晚才意识到这一点。 - supercat

1

这与使用IDisposable有关,而不是接口本身。您只需通过提供和覆盖受保护的Dispose(bool)方法来实现其使用的推荐模式 - 这不是接口本身的一部分。

如果您的重写方法实际上没有添加任何内容,则可以省略它。警告CA1063是为了向您突出显示问题,但如果您知道在您的情况下实际上没有要处理的资源,则可以通过提供您拥有的空实现或通过为此特定文件排除此特定规则来解决此问题。

您可以使用[SuppressMessage]属性来实现这一点。


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