CSharp 之 Dispose Pattern

最近再做项目代码的静态代码检查Sonar Lint清理的事情,其中有一项清理一直摸不着头脑。

静态代码检查器告诉我,我实现了一个IDispose的类需要正确实现Dispose Pattern。那么什么是正确的Dispose Pattern呢?

参考文献:

MSDN:
https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose

Sonar Lint Guide:
https://rules.sonarsource.com/csharp/RSPEC-3881

Dispose Pattern 的四个原则

(推荐) 如果我在一个非基类上实现了 IDispose 接口,则需要封装此类,加 sealed 修饰词。 Dispose 必须是 public 方法。

// Sealed class
public sealed class Foo1 : IDisposable
{
    public void Dispose()
    {
        // Cleanup
    }
}

(推荐) 如果我在一个基类上实现了 IDispose 接口,则必须实现一个 Virtural 的 Dispose(bool) 方法,并且在 Dispose 的实现里调用 Dispose(true)。

// Simple implementation
public class Foo2 : IDisposable
{
    public void Dispose() // Dispose Pattern standard implementation
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        // Cleanup
    }
}

如果我的一个类上实现了析构器,则必须在析构器里面调用 Dispose(false)。

// Implementation with a finalizer
public class Foo3 : IDisposable
{
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        // Cleanup
    }

    ~Foo3()
    {
        Dispose(false);
    }
}

如果一个类继承自实现了 IDispose 接口并且遵循 Dispose Pattern 的基类,则它在重写 Dispose(bool) 的时候必须要调用基类的 Dispose(bool) 方法。

// Base disposable class
public class Foo4 : DisposableBase
{
    protected override void Dispose(bool disposing)
    {
        // Cleanup
        // Do not forget to call base
        base.Dispose(disposing);
    }
}

那么我们应该如何实现 Dispose() 和 Dispose(bool) 方法呢?

Dispose()

Dispose() 方法必须是 public 而且是不能被重写的。在Dispose Pattern里面,它的唯一作用就是显式调用 Dispose(bool) 方法。

下面是 Dispose Pattern 对 Dispose() 方法的标准实现。

public void Dispose()
{
   // Dispose of unmanaged resources.
   Dispose(true);
   // Suppress finalization.
   GC.SuppressFinalize(this);
}

Dispose() 方法应回收所有非托管资源。调用GC.SuppressFinalize(this) 会告诉垃圾回收器我已经回收过了,因此不再调用它的析构器。

Dispose(bool)

使用一个 bool 的参数的作用是用来表示当前的调用是不是来自它的 Dispose 方法,因为析构器也同样会调用它。

Dispose(bool) 方法应该实现两个模块,分别是释放托管资源和非托管资源。

非托管资源:无论何时都应释放。

托管资源:需要释放这个对象里包含的 IDisposable 对象资源,以及申请了大量内存(例如大数组等)的资源。重要:只有参数为 true 才释放托管资源。

解释:如果从析构器调用的 Dispose(false) 只有非托管资源会被释放。因为垃圾回收器会回收该对象的时候,不确定Dispose方法调用和回收的顺序。通过传入 false 确保要回收的资源没有事先已经被回收。(有点没看懂,附上原文)

If the method call comes from a finalizer (that is, if disposing is false), only the code that frees unmanaged resources executes. Because the order in which the garbage collector destroys managed objects during finalization is not defined, calling this Disposeoverload with a value of false prevents the finalizer from trying to release managed resources that may have already been reclaimed.

// Implementation with a finalizer
public class Foo3 : IDisposable
{
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        // Free unmanaged resources

        if (disposing)
        {
            // Free managed resouces
        }
    }

    ~Foo3()
    {
        Dispose(false);
    }
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注