我找不到对它的引用,但我记得读过在析构函数或 IDisposable 的 Dispose() 方法中调用虚拟(多态)方法不是一个好主意。
这是真的吗,如果是这样,有人可以解释为什么吗?
我找不到对它的引用,但我记得读过在析构函数或 IDisposable 的 Dispose() 方法中调用虚拟(多态)方法不是一个好主意。
这是真的吗,如果是这样,有人可以解释为什么吗?
从 finalizer/ 调用虚方法Dispose
是不安全的,出于同样的原因,在构造函数中这样做是不安全的。无法确定派生类尚未清除虚方法正确执行所需的某些状态。
有些人对标准的 Disposable 模式及其对虚拟方法的使用感到困惑virtual Dispose(bool disposing)
,并认为这使得在 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();
}
}
这是当您 Dispose 和 type 的对象时发生的情况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.Whatever()
已经释放的方法D.X
现在,大多数运行此代码的人都会做一件事来修复它——他们base.Dispose(dispose)
在清理自己的对象之前将调用移至。而且,是的,这确实有效。但是,您真的相信 Programmer X,来自您开发的公司的 Ultra-Junior Developer C
,被分配到 write D
,以一种检测到错误或base.Dispose(disposing)
在正确位置调用的方式编写它?
我并不是说您永远不应该编写从 Dispose 调用虚拟方法的代码,只是您需要记录该虚拟方法的要求,即它永远不会使用在下面派生的任何类中定义的任何状态C
。
在构造函数和析构函数中都不鼓励使用虚方法。
原因比任何事情都更实际:可以以覆盖器选择的任何方式覆盖虚拟方法,并且必须确保在构造过程中初始化对象等事情,以免最终得到一个具有随机空值和无效的对象状态。
我不相信有任何反对调用虚拟方法的建议。您要记住的禁令可能是禁止在终结器中引用托管对象的规则。
.Net 文档中有一个标准模式定义了如何实现 Dispose()。该模式设计得非常好,应该密切关注。
要点是:Dispose() 是调用虚拟方法 Dispose(bool) 的非虚拟方法。布尔参数指示该方法是从 Dispose() (true) 还是对象的析构函数 (false) 调用的。在每个继承级别,都应该实现 Dispose(bool) 方法来处理任何清理工作。
当 Dispose(bool) 被传递值 false 时,这表明终结器已经调用了 dispose 方法。在这种情况下,只应尝试清理非托管对象(在某些极少数情况下除外)。原因是垃圾收集器刚刚调用了 finalize 方法,因此当前对象必须已标记为准备完成。因此,它引用的任何对象也可能已被标记为 read-for-finalization,并且由于序列是非确定性的,因此最终化可能已经发生。
我强烈建议您在 .Net 文档中查找 Dispose() 模式并准确地遵循它,因为它可能会保护您免受奇怪和困难的错误的影响!
为了扩展 Jon 的答案,如果您需要处理该级别的资源,您应该覆盖子类上的 dispose 或析构函数,而不是调用虚拟方法。
虽然,我不相信这里的行为有“规则”。但一般的想法是,您希望将资源清理仅隔离到该实施级别的该实例。