I can't find a reference to it but I remember reading that it wasn't a good idea to call virtual (polymorphic) methods within a destructor or the Dispose() method of IDisposable.
Is this true and if so can someone explain why?
Calling virtual methods from a finalizer/Dispose
is unsafe, for the same reasons it is unsafe to do in a constructor. It is impossible to be sure that the derived class has not already cleaned-up some state that the virtual method requires to execute properly.
Some people are confused by the standard Disposable pattern, and its use of a virtual method, virtual Dispose(bool disposing)
, and think this makes it Ok to use any virtual method durring a dispose. Consider the following code:
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();
}
}
Here's what happens when you Dispose and object of type D
, called d
:
((IDisposable)d).Dispose()
C.IDisposable.Dispose()
calls the virtual method D.Dispose(bool)
D.Dispose(bool)
disposes of D.X
D.Dispose(bool)
calls C.Dispose(bool)
statically (the target of the call is known at compile-time)C.Dispose(bool)
calls the virtual methods D.DoSomething()
D.DoSomething
calls the method D.X.Whatever()
on the already disposed D.X
Now, most people who run this code do one thing to fix it -- they move the base.Dispose(dispose)
call to before they clean-up their own object. And, yes, that does work. But do you really trust Programmer X, the Ultra-Junior Developer from the company you developed C
for, assigned to write D
, to write it in a way that the error is either detected, or has the base.Dispose(disposing)
call in the right spot?
I'm not saying you should never, ever write code that calls a virtual method from Dispose, just that you'll need to document that virtual method's requirement that it never use any state that's defined in any class derived below C
.