只要层次结构中的所有类都只使用托管资源,就可以使用虚拟Dispose()方法吗

本文关键字:可以使 虚拟 Dispose 方法 资源 层次结构 | 更新日期: 2023-09-27 18:10:48

我知道实现dispose模式的一般准则警告不要实现虚拟Dispose()。然而,大多数情况下,我们只处理类内的托管资源,因此完全处置模式似乎有些过头了——也就是说,我们不需要终结器。在这种情况下,在基类中有一个虚拟Dispose()可以吗?

考虑这个简单的例子:

abstract class Base : IDisposable
{
    private bool disposed = false;
    public SecureString Password { get; set; } // SecureString implements IDisposable.
    public virtual void Dispose()
    {
        if (disposed)
            return;
        if (Password != null)       
            Password.Dispose();     
        disposed = true;
    }
}
class Sub : Base
{
    private bool disposed = false;
    public NetworkStream NStream { get; set; } // NetworkStream implements IDisposable.
    public override void Dispose()
    {   
        if (disposed)
            return;
        if (NStream != null)
            NStream.Dispose();
        disposed = true;
        base.Dispose();
    }
}

我发现这比一个完整的处置模式更可读。我知道,如果我们在Base类中引入了一个非托管资源,那么这个例子就会出现问题。但假设这不会发生。那么,上面的例子是否有效/安全/稳健,或者它是否会带来任何问题?即使不使用非托管资源,我是否应该坚持标准的全面处置模式?或者,在这种情况下,上述方法确实完全可以吗?根据我的经验,这种情况比处理非托管资源更常见?

只要层次结构中的所有类都只使用托管资源,就可以使用虚拟Dispose()方法吗

在不处理非托管资源的情况下,我已经放弃了全面的IDisposable模式。

如果可以确保派生类不会引入非托管资源,那么我看不出有任何理由不能放弃该模式并使Dispose虚拟化!

(这里存在一个问题,因为不能强制执行这一点。你无法控制派生类将添加哪些字段,因此虚拟Dispose没有绝对的安全性。但是,即使你使用了全面的模式,派生类也可能会出错,所以总是有一些信任涉及到子类型遵守某些规则。(

首先,在我们只处理托管对象的情况下,拥有终结器是毫无意义的:如果从终结器调用Dispose(根据全面模式,Dispose(disposing: false)(,我们可能无法安全地访问/取消引用其他引用类型的托管对象,因为它们可能已经不在了。只有从正在完成的对象可访问的值类型对象才能安全访问。

如果终结器没有意义,那么使用disposing标志也没有意义,该标志用于区分Dispose是显式调用的还是终结器调用的。

如果没有终结器,也没有必要执行GC.SuppressFinalize

我甚至不确定Dispose在仅受管理的场景中是否仍然必须不抛出任何异常。AFAIK这个规则的存在主要是为了保护终结器线程(请参阅下面的@usr评论。(

正如您所看到的,一旦终结器结束,许多经典的一次性模式就不再必要了。

我知道,如果我们在基类中引入了一个非托管资源,这个例子会有问题。但假设这不会发生。那么,上面的例子是否有效/安全/稳健,或者它是否会带来任何问题?即使不使用非托管资源,我是否应该坚持标准的全面处置模式?

尽管基类只使用托管资源(这种情况在将来不会改变(,但不能保证派生类不会使用非托管资源。因此,考虑在基类(一个具有虚拟Dispose(bool)但没有终结器的类(中实现基本Dispose模式,即使您总是使用true调用Dispose(bool)方法。

当某一天从使用非托管资源的Base派生出一个新的子类时,其终结器可能希望用false调用Dispose(bool)。当然,您可以在派生类中引入一个新的Dispose(bool)

public class SubUnmanaged : Base
{
    IntPtr someNativeHandle;
    IDisposable someManagedResource;
    public override sealed void Dispose()
    {   
        base.Dispose();
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    ~SubUnmanaged();
    {
        base.Dispose();
        Dispose(false);
    }
    protected virtual void Dispose(bool disposing)
    {
        if (someNativeHandle != IntPtr.Zero)
            Free(someNativeHandle);
        if (disposing)
           someManagedResource.Dispose();
    }
}

但我觉得这有点令人讨厌。通过密封原始的虚拟Dispose()方法,可以防止由于多个虚拟Dispose方法而混淆进一步的继承者,并且可以从终结器和密封的Dispose()调用公共base.Dispose()。您必须在每个子类中执行相同的解决方法,这些子类使用非托管资源,并且派生自Base或完全托管的子类。但这已经与模式相去甚远,这总是让事情变得困难。

相关文章: