I have 2 assemblies, A containing the Main method and the Foo class, that uses Bar the class from assembly B:
Bar assembly (assembly B):
public sealed class Bar : IDisposable {
/* ... */
public void Dispose() { /* ... */ }
}
Foo class (assembly A):
public class Foo : IDisposable {
private readonly Bar external;
private bool disposed;
public Foo()
{
Console.WriteLine("Foo");
external = new Bar();
}
~Foo()
{
Console.WriteLine("~Foo");
this.Dispose(false);
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
if (disposing) external.Dispose();
disposed = true;
}
}
Entry point (in assembly A):
class Program
{
static void Main(string[] args)
{
try
{
var foo = new Foo();
Console.WriteLine(foo);
}
catch (FileNotFoundException ex)
{
// handle exception
Console.WriteLine(ex.ToString());
}
Console.ReadLine();
}
}
One of the requirements for this piece of software is that it must gracefully handle the case when a dll is missing.
So when I delete assembly B, and start the application I would expect that the try catch block in the main method handles the FileNotFoundException thrown when the assembly B is missing. Which it sort of does, but that is where the problems start...
When the application continues (a line is entered in the console), the finalizer of the Foo class is called (?!) although no instance of Foo was created - the constructor hasn't been called. Since there is no instance of the class there is no way for me to call GC.SupressFinalize on the instance externally. The only thing you see in the console output when running the project without the B assembly is ~Foo.
So the questions:
- Why is the finalizer called even though no instance of the class is created? (to me it makes absolutely no sense! I would love to be enlightened)
- Is it possible to prevent the application from crashing without a try-catch block in the finalizer? (this would mean refactoring the whole code base...)
Some background: I encountered this problem when writing a plugin enable enterprise application with the requirement that it must continue operation if a dll is missing in the plugin deployment folder and flagging the faulty plugin. I figured that the try-catch block around the external plugin loading procedure would suffice, but obviously it doesn't, since after catching the first exception the finalizer is still invoked (on the GC thread), which finally crashes the application.
Remark The above code is the most minimalistic code I could write to reproduce the exception in the finalizer.
Remark 2 If I set the breakpoint in the Foo constructor (after deleting Bar's dll) it is not hit. This means if I would set have a statement in the constructor that creates a critical resource (before newing up Bar) it wouldn't be executed, hence no need for the finalizer to be called:
// in class Foo
public Foo() {
// ...
other = new OtherResource(); // this is not called when Bar's dll is missing
external = new Bar(); // runtime throws before entering the constructor
}
protected virtual void Dispose(bool disposing) {
// ...
other.Dispose(); // doesn't get called either, since I am
external.Dispose(); // invoking a method on external
// ...
}
Remark 3 An obvious solution would be to implement the IDisposable like below, but that means breaking the reference pattern implementation (even FxCop would complain).
public abstract class DisposableBase : IDisposable {
private readonly bool constructed;
protected DisposableBase() {
constructed = true;
}
~DisposableBase() {
if(!constructed) return;
this.Dispose(false);
}
/* ... */
}