What’s the difference between a destructor and a finalizer?

What’s the difference between a destructor and a finalizer?

Rate This
  • Comments 20

Today, another dialogue, and another episode of my ongoing series "what's the difference?"

What’s the difference, if any, between a “destructor” and a “finalizer”?

Both are mechanisms for cleaning up a resource when it is no longer in use. When I was asked this, at first I didn’t think there was a difference. But some Wikipedia searches turned up a difference; the term “destructor” is typically used to mean a deterministically-invoked cleanup, whereas a “finalizer” runs when the garbage collector says to run it.

Doesn’t that mean that the C# spec uses the term “destructor” incorrectly?

Yes, by these definitions, the C# spec gets it wrong. What we call a “destructor” in the spec is actually a finalizer, and what we call the “Dispose()” method invoked by a “using” statement is in fact a “destructor”.

The CLI spec calls the finalizer by its right name.

Why did the authors of the C# spec get it wrong?

I don't know, but I can guess. I have two guesses.

Guess #1 is that on May 12th, 1999 there was not a Wikipedia article clearly describing the subtle difference between these two concepts. That's because there wasn't a Wikipedia. Remember back when there wasn't a Wikipedia? Dark ages, man. The error might simply have been an honest mistake, believing that the two terms were identical.

Heck, for all I know, the two terms were identical on May 12th, 1999, and the difference in definitions only evolved later, as it became obvious that there was a need to disambiguate between eager/deterministic and lazy/nondeterministic cleanup methods. Anyone who has more historical perspective on this than I do, feel free to chime in here.

Guess #2 is that on May 12th, 1999, the language design committee wished to leave open the possibility that a C# "destructor" could be implemented as something other than a CLR finalizer. That is, the "destructor" was designed to be a C# language concept that did not necessarily map one-to-one with the CLR’s "finalizer" concept.

When designing a language at the same time as the framework it sits atop is also being designed, sometimes you want to insulate yourself against late-breaking design changes in your subsystems. Deliberately preventing name conflation is one way to do that.

What’s your sudden obsession with May 12th, 1999 about?

The language committee's notes for May 12th 1999 read in part:

We're going to use the term "destructor" for the member which executes when an instance is reclaimed. Classes can have destructors; structs can't. Unlike in C++, a destructor cannot be called explicitly. Destruction is non-deterministic – you can't reliably know when the destructor will execute, except to say that it executes at some point after all references to the object have been released. The destructors in an inheritance chain are called in order, from most descendant to least descendant.  There is no need (and no way) for the derived class to explicitly call the base destructor. The C# compiler compiles destructors to the appropriate CLR representation.  For this version that probably means an instance finalizer that is distinguished in metadata. 

Notice that this supports my hypothesis that the language design team was attempting to insulate themselves from becoming tied to a particular CLR term.

  • is there any link that the language commette notes are actually recorded on May 12th of 1999. Please share the link.

  • @Tanveer Badar who said:

    "Wrong actually. Destructor, as in C++, is always called except for pathological conditions like throwing exceptions from one destructor when unwinding is already in progress etc. 'using( )' on the other hand is entirely optional in C#."

    You've got your analogy between C++ and C# wired up wrongly, so steady on with the "wrong actually"!

    This is how to request automatic deterministic destruction in C#. The first line enables it, the second does not:

    using (var aCSharpVar = new ACSharpClass()) { .. scope... }

    var aCSharpVar = new ACSharpClass();

    And this is how to make the same distinction in C++:

    ACppClass aCppVar;

    ACppClass *aCppPtr = new ACppClass();

    In other words, it is optional in BOTH languages. The entire subject of "exception safety" in C++ exists only because the second option is always available. If you make the wrong choice, destructors are NOT automatically called during stack unwind.

    The only difference is that each language makes a different choice for which should be the most succinct and readable - the one it expects its users to most frequently use. And that in turn is influenced by the assumed presence/absence of an asynchronous GC.

    So on the "consumer" side of automatic cleanup, the only difference is a matter of emphasis. The capabilities are identical.

    The "implementation" side is another matter - I would love to see language support in C# for implementing IDisposable, such that specific fields in a class can be marked as "ownership" relationships, i.e. the boilerplate of Dispose would get written automatically as in C++/CLI. The use of special language syntax for finalizers in C# does in retrospect seem like a strange choice, because now the general advice is that we almost never need to write a finalizer - they are a highly specialized, advanced and error-prone area. And yet implementing IDisposable is very common (turns out that memory is not the only resource!) and involves much tedious boilerplate in a big containment/inheritance hierarchy. So language support for implementing IDisposable would make a lot more sense than language support for writing finalizers.

    @Gabe - "Come to think of it, isn't having destructors useless without being able to override the assignment (=) operator?"

    On value types, pretty much, because value types get copied when they are assigned. As in C++, if you need a special destructor, you'd need a special assignment/copy-constructor.

    On reference types, there's no such issue. Destructors (Dispose) are extremely useful and assignment is only a problem because it allows us to accidentally lose track of an object without disposing it. But we have 'readonly' to help there.

    Suppose the keyword 'using' was overloaded (again) to become a new modifier on field declarations:

    class A { using B _myB = new B(); }

    This would fail to compile if B did not implement IDisposable. It basically would cause A to implement IDisposable by forwarding Dispose on to _myB.

    Now, the fact that we can assign a new object to _myB creates a "hole" into which undisposed B instances might fall, but we can avoid that in a simple way by also marking the field as readonly:

    class A { using readonly B _myB = new B(); }

    That says that an A has a single associated B whose lifetime is bounded by the lifetime of the A. This could also allow the compiler/CLR to optimise by nesting the B instance inside the storage of the A, reducing the number of separate objects visible to the GC.

  • There's more fundamental distinction between destructors and finalizers (than whether they are called deteministically).

    Imagine you want to implement a "proper" garbage collection (GC) mechanism in C++. C++ has destructors. (When you delete an object, it's destructor gets called.) The question is: Can the GC reuse the C++ destructors for finalizers? The answer is: No, it can't, it wouldn't work.

    The reason is in that destructors assume that objects are COMPLETE at the time of destruction, e.g. all member pointers point to live objects. When you delete an object, it's destructor is called first, and only then the destructors of child objects are called. The destruction order is completely under control of the programmer.

    On the other hand, when GC begins to delete objects, the deletion is performed in random order. At the time when an object is being finalized, its child objects might already have been finalized.

    Thus, changing memory management method from e.g. reference counting to mark-and-sweep garbage collection cannot be transparent, you'd have to rewrite the destructors in order to work in the GC environment. (And when changing back to reference counting, you'd have to rewrite them again. They cannot be written so that they work under both memory management methods.) That's why destructors and finalizers are in fact completely different types of methods.

    BTW, this may also explain why in managed C++ the syntax "~X()" is a shortcut for the Dispose method, not for the finalizer.

  • Xarx, you are WRONG in your post:

    "BTW, this may also explain why in C# the syntax "~X()" is a shortcut for the Dispose method, not for the finalizer."

    "~X()" is the C# styntax for implementing the FINALIZER.


    Destructors are the C# mechanism for performing cleanup operations. Destructors provide appropriate safeguards, such as automatically calling the base type's destructor. In C# code, Object..::.Finalize cannot be called or overridden.

    I think Xarx just made a typo. I think Xarx meant to say "in C++ the syntax". I'll fix it. -- Eric

  • Hi Eric!

    One idea for "What’s the difference between"-series:

    Reflection (& attributes) vs expression trees

    Both are metaprogramming ways to manipulate "code as data"-structures..

Page 2 of 2 (20 items) 12