Freigeben über


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

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.

Comments

  • Anonymous
    January 21, 2010
    Guess #3: first a decision was made to use the C++ ~ClassName() syntax for it (likely for the same reasons why colon is used for base class list etc), and then it was called "destructor" because that's what a thing that looks like that is called in C++.

  • Anonymous
    January 21, 2010
    "The C# spec" isn't an unambiguous term either, of course... there's the Microsoft version and the ECMA version. ECMA-334 (4th edition) has this to say in section 17.12: [Note: In the previous version of this standard, what is now referred to as a "finalizer" was called a "destructor". Experience has shown that the term "destructor" caused confusion and often resulted to incorrect expectations, especially to programmers knowing C++. In C++, a destructor is called in a determinate manner, whereas, in C#, a finalizer is not. To get determinate behavior from C#, one should use Dispose. end note] Further points of confusion I've noted in the past: due every value type have a parameterless constructor or not? It does according to C#, but not according to the CLI spec. Then there's the concept of a static constructor, which doesn't exist in the CLI - the distinction that C# makes between a type with a static constructor and one with only static variable initializers is only present in terms of the beforefieldinitflag (AFAIK). It's a good job this doesn't impact on regular developers most of the time :)

  • Anonymous
    January 21, 2010
    It is interesting that you are concerned about the historical reason why something is wrong in Software Engineering. I personally consider the "historically why" irrelevant when it comes to Software. All that matters is what is correct so you can do my job well. No one will pay you on a job to tell them why var is not a keyword as some author implicitly imply. However, many will pay you to use it in your code.

  • Anonymous
    January 21, 2010
    In my opinion, the choice of "~ClassName" as the finalizer syntax is single worst mistake in the design of C#. It needlessly confuses people. C++/CLI has a very elegant solution: a new syntax ("!ClassName" ) for a new concept.

  • Anonymous
    January 21, 2010
    @Zoldello: Understanding the historical perspective may not be very important for fixing today's problem. But it is QUITE important for making sure that we don't repeat the mistakes of the past. Now, I'm not sure that Eric is going to be designing a new language anytime soon. But I certainly hope that anyone who is planning to design a new language is reading Eric's blog because of wonderful little tidbits like this that help elucidate the pitfalls and details that are important for an undertaking as difficult as language (and compiler) design.

  • Anonymous
    January 21, 2010
    It is called a destructor because syntactically, that is what it is in a c-style language.  Whether or not c# has deterministic finalization is another thing entirely.  The using pattern merely mimics the behavior of  deterministic desctruction and relies on proper implementation to ensure that later finalization attempts are suppressed.

  • Anonymous
    January 21, 2010
    > due every value type have a parameterless constructor or not? It does according to C#, but not according to the CLI spec. There's no contradiction here, since two concepts need not map one-to-one. So every value type has a parameterless public constructor in C#, but such a constructor needs not be represented by a constructor on IL level.

  • Anonymous
    January 21, 2010
    @Pavel: I agree that the two concepts don't have to map... it's just surprising when they don't.

  • Anonymous
    January 21, 2010
    I remember that the issue of determinsitic vs. non-deterministic destruction caused quite a few of the more heated discussions back in late 2000/early 2001, when .NET was still beta. See http://groups.google.de/group/microsoft.public.dotnet.languages.csharp/browse_thread/thread/55053af9d6ffaac4/6cf64cc6affd0778?hl=de&q=c%23+destructor+finalizer+%22alexander+jung%22 for example, with Eric Gunnerson among the participants :-D Eventually those discussions lead to the introduction of the disposable pattern (but we never got determinsitic destruction). Anyway, back then most developers looking into C# had C++ background, and where certainly alienated by this semantic change (especially since it invalidated a lot of idioms we were used to). However I'm a little surprised that this is even an issue today, a decade later...

  • Anonymous
    January 21, 2010
    The comment has been removed

  • Anonymous
    January 22, 2010
    "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”. " 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#. Best a compiler can do is generate a warning, or some some tool like FxCop yell at you that you forgot to dispose a disposable object. If C# wants to promote using( ) as a true destructor best approach would be to do what C++/CLI did. Reference types with stack allocated syntax and calling Dispose automatically upon exiting lexical scope if the type implements IDisposable.

  • Anonymous
    January 23, 2010
    If value types were allowed to have destructors they could be deterministic and improve the language considerably (IMHO). http://stackoverflow.com/questions/173670/why-is-there-no-raii-in-net

  • Anonymous
    January 23, 2010
    Motti: what sort of value types would use destructors anyway? I don't think I've ever seen one.

  • Anonymous
    January 24, 2010
    @zoldello You may not get paid for those things, but examining previously-made decisions in language design and observing the impact they have had on users is, in fact, one of the things Eric gets paid for. Why would it surprise you to know that Eric puts some thought into it? In addition, as a programmer, knowing the reasoning behind certain idioms, concepts, and so forth helps determine their advantages and disadvantages in practice. To pick a grand and well-worked over example, we use OOP because historically, straight-up procedural programming had several disadvantages when used in the large. Ignoring the why of this tends to lead to things like Eric's oft-mentioned "object happiness", or difficult-to-maintain procedural programming in disguise.

  • Anonymous
    January 26, 2010
    Come to think of it, isn't having destructors useless without being able to override the assignment (=) operator? I think it's a really messy way to avoid some using()s or Dispose() calls.

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

  • Anonymous
    January 31, 2010
    The comment has been removed

  • Anonymous
    February 10, 2010
    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.

  • Anonymous
    February 10, 2010
    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. http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx 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

  • Anonymous
    February 26, 2010
    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..