我找不到它的引用,但我记得读过在析构函数或IDisposable的Dispose()方法中调用虚拟(多态)方法不是一个好主意。
这是真的吗?若有人可以解释原因?
答案 0 :(得分:5)
从终结器/ Dispose
调用虚拟方法是不安全的,原因与it is unsafe to do in a constructor相同。无法确定派生类是否还没有清除虚拟方法正确执行所需的某些状态。
有些人对标准的Disposable模式及其使用虚拟方法virtual Dispose(bool disposing)
感到困惑,并且认为这样可以在处理中使用任何虚拟方法。请考虑以下代码:
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和名为D
的{{1}}类型的对象时发生的情况:
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
调用移动。而且,是的,这确实有效。但是你真的相信程序员X,你开发base.Dispose(dispose)
的公司的Ultra-Junior Developer,被指派编写C
,以一种检测到错误的方式编写它,或者具有D
在正确的地方打电话?
我不是说你永远不应该永远编写从Dispose调用虚方法的代码,只需要记录那个虚方法要求它永远不会使用在base.Dispose(disposing)
下面派生的任何类中定义的任何状态。
答案 1 :(得分:1)
不鼓励构造函数和析构函数中的虚方法。
原因比任何事情都更实际:虚拟方法可以通过覆盖者选择的任何方式覆盖,并且在构造期间对象初始化之类的事情,例如,必须确保以免最终结束对象具有随机空值和无效状态。
答案 2 :(得分:1)
我不相信有任何反对调用虚方法的建议。您记住的禁令可能是在终结器中引用托管对象的规则。
有一个标准模式可以定义.Net文档,了解Dispose()应该如何实现。图案设计得非常好,应该严格遵循。
要点是这样的:Dispose()是一个非虚方法,它调用虚方法Dispose(bool)。 boolean参数指示是从Dispose()(true)还是从对象的析构函数(false)调用方法。在每个继承级别,应该实现Dispose(bool)方法来处理任何清理。
当Dispose(bool)传递值false时,这表示终结器已调用dispose方法。在这种情况下,只应尝试清除非托管对象(在某些罕见情况下除外)。原因是垃圾收集器刚刚调用了finalize方法,因此必须将当前对象标记为ready-for-finalization。因此,它引用的任何对象也可能已被标记为read-for-finalization,并且由于序列是非确定性的,因此可能已经完成了最终化。
我强烈建议在.Net文档中查找Dispose()模式并准确地关注它,因为它可能会保护您免受奇怪和困难的错误!
答案 3 :(得分:0)
要扩展Jon的答案,如果您需要处理该级别的资源,则应该覆盖子类上的dispose或析构函数,而不是调用虚方法。
虽然,我不相信这里的行为有“规则”。但一般的想法是,您希望将资源清理仅隔离到该实现级别的实例。