可以从Dispose或者析构函数调用虚拟方法吗?

时间:2020-03-06 14:45:50  来源:igfitidea点击:

我找不到对此的引用,但我记得读过,在析构函数中调用虚拟(多态)方法或者IDisposable的Dispose()方法不是一个好主意。

这是真的吗?如果可以的话,有人可以解释为什么吗?

解决方案

在构造函数和析构函数中均不建议使用虚方法。

原因比任何东西都更实际:可以用覆盖程序选择的任何方式覆盖虚拟方法,例如,必须确保在构造过程中进行对象初始化之类的操作,以免最终得到带有随机空值和无效值的对象状态。

为了扩展Jon的答案,如果我们需要在该级别上处理资源,则应该在子类上覆盖dispose或者destructor,而不是调用虚拟方法。

虽然,我不认为此处的行为有"规则"。但是一般的想法是,我们希望将资源清理仅隔离到该实现级别的那个实例。

我认为不存在反对调用虚方法的任何建议。我们要记住的禁止可能是禁止在终结器中引用托管对象的规则。

有一个标准模式定义了.Net文档,以说明应如何实现Dispose()。该模式的设计非常好,应密切注意。

要点是:Dispose()是一种非虚拟方法,它调用虚拟方法Dispose(bool)。布尔参数指示是从Dispose()(true)还是从对象的析构函数(false)调用该方法。在继承的每个级别,都应实现Dispose(bool)方法来处理所有清理。

当Dispose(bool)传递值为false时,这表明终结器已调用了dispose方法。在这种情况下,仅应尝试清除非托管对象(在某些罕见情况下除外)。这样做的原因是垃圾收集器刚刚调用了finalize方法,因此当前对象必须已标记为可以完成。因此,它引用的任何对象也可能已标记为"可读以最终确定",并且由于该序列不确定,因此最终确定可能已经发生。

我强烈建议我们在.Net文档中查找Dispose()模式,并严格按照它进行操作,因为它可能会保护我们免受怪异和棘手的错误的侵害!

从finalizer /Dispose调用虚拟方法是不安全的,出于同样的原因,在构造函数中执行虚拟方法也是不安全的。无法确定派生类尚未清除虚拟方法需要正确执行的某些状态。

一些人对标准的Disposable模式及其使用的虚拟方法" virtual Dispose(bool dispose)"感到困惑,并认为可以在处理过程中使用任何虚拟方法。考虑以下代码:

class C : IDisposable {
    private IDisposable.Dispose() {
        this.Dispose(true);
    }
    protected virtual Dispose(bool disposing) {
        this.DoSomething();
    }

    protected virtual void DoSomething() {  }
}
class D : C {
    IDisposable X;

    protected override Dispose(bool disposing) {
        X.Dispose();
        base.Dispose(disposing);
    }

    protected override void DoSomething() {
        X.Whatever();
    }
}

这是当我们处理和释放类型为D的对象d时发生的情况:

  • 一些代码调用(((IDisposable)d).Dispose()
  • C.IDisposable.Dispose()调用虚拟方法D.Dispose(bool)
  • D.Dispose(bool)处理D.X
  • D.Dispose(bool)静态地调用C.Dispose(bool)(在编译时就知道了调用的目标)
  • C.Dispose(bool)调用虚拟方法D.DoSomething()
  • D.DoSomething在已经处理过的D.X上调用方法D.X.Whatever()

现在,大多数运行此代码的人都会做一件事来对其进行修复-在清理自己的对象之前,他们将base.Dispose(dispose)调用移至。而且,是的,确实有效。但是我们真的信任程序员X,我们开发C的公司的超中级开发人员,被分配为写D的方式,以便以检测到错误或者具有错误为基础的方式来编写它。 (处置)在正确的位置打电话?

我并不是说我们永远不要编写从Dispose调用虚拟方法的代码,只是我们需要记录该虚拟方法的要求,即它绝不使用在C之下派生的任何类中定义的任何状态。