CA1063:正确实现 IDisposable

属性
规则 ID CA1063
标题 正确实现 IDisposable
类别 设计
修复是中断修复还是非中断修复 非中断
在 .NET 8 中默认启用

原因

System.IDisposable 接口无法正确实现。 可能的原因包括:

  • 在类中重新实现 IDisposable
  • 再次重写 Finalize
  • 重写 Dispose()
  • Dispose() 方法是非公用、已密封或命名为“Dispose”。
  • Dispose(bool) 未受保护、虚拟或未密封。
  • 在未密封类型中,Dispose() 必须调用 Dispose(true)
  • 对于未密封的类型,Finalize 实现不调用或不同时调用 Dispose(bool) 或基类终结器。

违反其中任何一个模式都会触发警告 CA1063。

声明和实现 IDisposable 接口的每个未密封类型都必须提供自己的 protected virtual void Dispose(bool) 方法。 Dispose() 应该调用 Dispose(true),而终结器应该调用 Dispose(false)。 如果创建声明和实现 IDisposable 接口的未密封类型,则必须对 Dispose(bool) 进行定义和调用。 有关详细信息,请参阅清理非托管资源(.NET 指南)实现 Dispose 模式

默认情况下,此规则仅查看外部可见的类型,但这是可配置的。

规则说明

所有 IDisposable 类型都应正确实现 Dispose 模式

如何解决冲突

检查代码,并确定以下哪种解决方法能解决此冲突:

  • 从类型实现的接口列表中移除 IDisposable,并重写 Dispose 基类实现。

  • 从类型中移除终结器,重写 Dispose(bool disposing),并在“disposing”为 false 的代码路径中加入终结逻辑。

  • 重写 Dispose(bool disposing),并在“disposing”为 true 的代码路径中加入释放逻辑。

  • 确保将 Dispose() 声明为公用且已密封

  • 将 dispose 方法重命名为“Dispose”,并确保将其声明为公用且已密封

  • 确保 Dispose(bool) 声明为受保护、虚拟和未密封。

  • 修改 Dispose(),使其调用 Dispose(true),并在当前对象实例(在 Visual Basic 中为 thisMe)上调用 SuppressFinalize,然后返回。

  • 修改终结器,使其调用 Dispose(false),然后返回。

  • 如果创建声明和实现 IDisposable 接口的未密封类型,请确保 IDisposable 的实现遵循本节前面所介绍的模式。

何时禁止显示警告

不禁止显示此规则发出的警告。

注意

如果满足以下所有条件,你可能会看到来自此规则的误报警告:

  • 你将 Visual Studio 2022 版本 17.5 或更高版本与旧版 .NET SDK(即 .NET 6 或更低版本)配合使用。
  • 你使用的是 .NET 6 SDK 中的分析器或更低版本的分析器包,例如 Microsoft.CodeAnalysis.FxCopAnalyzers。
  • 你在 IDispose 实现上有属性。

在这种情况下,禁止显示误报警告没有风险。 误报是由于 C# 编译器中的中断性变更造成的。 请考虑使用更新的包含误报警告修补程序的分析器。 请升级到 Microsoft.CodeAnalysis.NetAnalyzers 版本 7.0.0-preview1.22464.1 或更高版本,或者使用 .NET 7 SDK 中的分析器。

配置代码以进行分析

使用下面的选项来配置代码库的哪些部分要运行此规则。

可以仅为此规则、为适用的所有规则或为适用的此类别(设计)中的所有规则配置此选项。 有关详细信息,请参阅代码质量规则配置选项

包含特定的 API 图面

你可以根据代码库的可访问性,配置要针对其运行此规则的部分。 例如,若要指定规则应仅针对非公共 API 图面运行,请将以下键值对添加到项目中的 .editorconfig 文件:

dotnet_code_quality.CAXXXX.api_surface = private, internal

伪代码示例

以下伪代码提供了有关如何在使用托管资源和本机资源的类中实现 Dispose(bool) 的常规示例。

public class Resource : IDisposable
{
    private bool isDisposed;
    private IntPtr nativeResource = Marshal.AllocHGlobal(100);
    private AnotherResource managedResource = new AnotherResource();

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

    // The bulk of the clean-up code is implemented in Dispose(bool)
    protected virtual void Dispose(bool disposing)
    {
        if (isDisposed) return;

        if (disposing)
        {
            // free managed resources
            managedResource.Dispose();
        }

        // free native resources if there are any.
        if (nativeResource != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(nativeResource);
            nativeResource = IntPtr.Zero;
        }

        isDisposed = true;
    }

    // NOTE: Leave out the finalizer altogether if this class doesn't
    // own unmanaged resources, but leave the other methods
    // exactly as they are.
    ~Resource()
    {
        // Finalizer calls Dispose(false)
        Dispose(false);
    }
}

另请参阅