none
Dispute: Destructor versus IDisposable RRS feed

  • General discussion

  • It's clear for me that destructor has slightly different meaning in .NET managed code comparing to unmanaged C++ code. Even the difference is usually presented as just "slightly", it is "slightly" on fatally principal place. And so this "slight" difference results to totally different behavior of destructor in managed and umnanaged code ...
    I accepted this difference ... even if I think it has been totally wrong decision (in design & in principle) what guys responsible for .NET managed code design did (may be, there is some hidden logical constraint why it is done in this way and it really can not be done differently ... hmmm).

    Now, the .NET managed code has two constructs as the result of this design decision - the destructor and the IDisposable . As the final result of it, as you can see now afte rome year of .NET framework existence, the destructor concept totally lost it's sense - even those guys responsible for .NET managed code design & principles are saying: "Do not use destructor".
    That is really funny and totally senseless outcome of this case => The "IDisposable::Dispose ()" approach replaced purposes of destructor and destructor lost sense and it is not used anymore ... ==> experienced people are asking: "Why this?".

    =====> There have been made a lot of discussions about this point - even I attended such discussions and spent a lot of time to discuss it ...
    My point of view is: Purpose of the destructor is to control life-cycle of an object instance and !NOT! that it will be called when the particular object instance itself is going to be freed from memory, absolutely !not!.
    Freeing of memory is just the last step which is not relevant to life-cycle of the object in GENERAL!
    Memory freeing/purging can be done by some independent functionality (e.g. garbage collector).
    Purpose of the destructor is to serve as FINALIZER of life-cycle of particular object instance - to do particular  functionlity to finalize the object (this functionality does not necessarily mean purging/freeing system resources, the functionality means here execute anything what necessary to to finalize the life-cycle :)
    That's it, that's all.

    Consider following approach:
    The IDisposable concept would be completely removed from managed .NET code principles and the destructor concept will be used instead of it there ==> OF COURSE then it is necessary to incorporate destructor concept in to the principles of the .NET managed code in the SAME idea/approach/behavior as the IDisposable had!
    The result of this approach is bellow (watch the '//* ...' comments):

    class A
    {
        //* The AutoResetEvent instance represents the system resource which
        //* life-cycle IS SUPPOSED to be controlled by life-cycle of the A class instance.
        AutoResetEvent m_ARE = new AutoResetEvent ();

        /* The destructor would replace role of the "IDisposable::Dispose()" method completely.
        ~A ()
        {
            m_ARE.Close(); //* Freeing system resource
        }

        void Method ()
        {
            using (A a = new A()) //* <===== important construct defines controlled life-cycle
            {
            //* ... some functionality could be implemented here ...
            } //* <====== The 'A::~A()' destructor would be called here
              //*         instead of the 'IDisposable::Dispose()'.
        }
    }

    What is the difference now? Hmmm, nothing, we just reduced two approaches/costructs (IDisposable andd destructor) to only one - the destructor. Why destructor and not IDisposable? Hmm, easy, the destructor approach is well known construct in many other languages, so why do not stick with it?

    Thanks & Best Regards,
    Peter
    Wednesday, September 9, 2009 5:55 PM

All replies

  • Even though the documentation says "destructor" still, (I've actually entered an issue on this), the preferred term in .NET isn't destructor anymore.  It's finalizer.  The finalizer is the method, which in C#, starts with the tilde (~) that performs any "last minute" operations just before the object is removed from memory. 

    There's nothing wrong with using the finalizer, in .NET, but typically, the finalizer is simply meant to perform the disposal on the object.  This is done so that any unmanaged resources can be cleaned up, in the event that the programmer doesn't clean it up using IDisposable.  It's sort of a "fallback" to ensure that things get cleaned up.  The typical way to implement it is like this:

    public class DisposableObject : IDisposable
    {
        // called by the using statement.
        public void Dispose()
        {
            // Pass true, we're explicitly disposing.
            Dispose(true);
        }

        ~DisposableObject()
        {
            // pass false, the GC is calling Dispose here.
            Dispose(false);   
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // reference other objects here.  This is being explicitly called.
            }

            // Call SuppressFinalize to ensure the finalizer is not run.
            GC.SuppressFinalize(this);
        }
    }

    With managed resources, there's no way of knowing for sure when the Garbage Collector will get around to collecting the class.  Unmanaged resources have to be explicitly freed.  IDisposable in conjunction with the finalizer will enable the programmer to free the resources as soon as possible, and will also ensure that the resources are cleared even in the event that the programmer "forgot".

    So according to the definition you've given above, you're right.  The "destructor", which is really the "finalizer" is meant to "finalize" and not "destruct". 


    Coding Light - Illuminated Ideas and Algorithms in Software
    Coding Light WikiLinkedInForumsBrowser
    Wednesday, September 9, 2009 6:05 PM
    Moderator
  • You are asking for explicit destruction to be brought back.  What's the point?  You've already got plenty of language choices where that's the required means of dealing with resource allocation.  With the usual baggage that goes along with that, such as resource leaks, circular references, copy constructors and inefficiency.

    Writing a finalizer is something you never do.  It is the job of the .NET framework classes to wrap operating resources that need to be finalized.  A language where you don't write destructors and don't have to call them is a great leap forward.  Having to use the using statement is a bit annoying.  But nothing horribly happens when you forget.

    Hans Passant.
    Wednesday, September 9, 2009 7:00 PM
    Moderator
  • Writing a finalizer is something you never do.  It is the job of the .NET framework classes to wrap operating resources that need to be finalized.  A language where you don't write destructors and don't have to call them is a great leap forward.  Having to use the using statement is a bit annoying.  But nothing horribly happens when you forget.

    Unless it is you wrapping the resources.  The .NET Framework provides several classes that wrap unmanaged resources, but if the class you're writing itself wraps unmanaged resources, then it is the responsibility of your class to release the unmanaged resources. 

    If you forget the using statement with say, a FileStream, you lock the file until the garbage collector gets around to it.  This could potentially crash an app when you try to get a lock on said file. 

    "Horrible" is a relative term here, I think. Will it bring down the OS?  No, probably not.  Will it cause problems with your own app, probably. 

    That being said, have I ever written a finalizer, except for demonstration purposes?  No.  The Framework wraps pretty everything I've ever wanted.
    Coding Light - Illuminated Ideas and Algorithms in Software
    Coding Light WikiLinkedInForumsBrowser
    Wednesday, September 9, 2009 7:06 PM
    Moderator
  • OK, Understand,
    but however - look what we have now - 3 different method which are supposed to do one simple thing - the "finalization functionality":

    1) void IDisposable::Dispose () - without parameters
    2) void DisposableObjec:Dispose (bool) with parameter
    3) ~DisposableObject() - destructor, finalizer or what ever we are going to call it.

    Why don't do it really simple -> to have just one single method (or concept) how to finalize object life-cycle, it does not matter what it will be - IDisposable::Dispose() or destructor method ... ??

    Freeing memory which has been occupied by the object instance shall NOT be role of finalization - this shall be managed by higher authority and without control of programmer (outer word) - e.g. garbage collector - we just should not care when and how the higher authority (e.g. garbage collector) manages memory pool - everything what is important for programmer is to have possibility/way ho to where to implement own customized finalization functionality (if programmer decides to handle it).

    IMPORTANT SAMPLE:
    The memory management/purging is totally, but really totally different and unrelated thing - for example, garbage collector can be based on following principles: It can preallocate some amount of memory and LET SAY the currently running "slim" program will never-ever need more memory than the preallocated amount.
    So what is important in this case? ===> The MEMORY occupied by ANY of object instances created by the program will be just RECYCLED by garbage collector and so it will be NEVER freed - the reason could the performance - garbage collector just keeps it preallocated and so spare time to unnecessary free/allocate cycles.
    Each object instance which is going to be destructed/disposed/finalized (or hat ever we will call it) will just JUST executes own destructor/dispose/finalize method and so the finalization functionality will be done ...  =======> But memory occupied by the object instance originally is just unimportant - garbage collector can just decide to recycle it and NOT free/delaocate it !!!!

    What I'm trying to say? ===> DO THINGS AS SIMPLE AS IT IS POSSIBLE & AS SAFE AS POSSIBLE
    The principles of the .NET managed code shall provide & allow JUST and ONLY single way how to implement destruction/disposing/finalization (or what ever we call it).

    From my point of view, the freeing of memory occupied by the managed object instance shall be manged exclusively by garbage collector logic, programmer shall not care how the memory itself is handled by garbage collector - see bellow:

    My final statement about behavior in managed code is:
    It does NOT have sense to propagate to outside word any of methods (like the destructor method is propagated now) supposed to be called when the memory occupied by the particular object instance is going to be freed ======> Senseless, since the point of execution of such method is exclusively connected to logic & principles & implementation of particular garbage collector and garbage collector can be later redesigned/reimplemented and so the same program (the same source code of the program) can result to different functional behavior depending just on garbage collector implementation!  This shall not be allowed, never!
    There shall be propagated just such methods to public word which are independent/unrelated to private principles of anything from .NET framework (e.g. implementation of the garbage collector).
    Everything what could differ eventually, when a garbage collector implementation will be changed, is that particular PERFORMANCE abilities will be affected, but definitely NOT that some of implemented methods will be executed in different order or some of them will not be executed all!

    Uff, howk.

    Thanks & Best Regards,
    Peter

    Wednesday, September 9, 2009 7:48 PM
  • What you are missing here is that most likely the designers of .Net went through many different scenarios, and their outcomes kept modifying their design until they arrived to the design we know today.  I bet you would complain about the design of COM/ActiveX if you were a C++ programmer and just learned that how-to without the why-is.  I kind of used to complain about it until I read Don Box.  Don's book clearly explains why things are the way they are.  Can they be better?  Maybe, maybe not.  I'm just not willing to go through what they went through to design COM.

    Also note that you want the word "destructor" to have the same meaning in .Net as well as C++, for example.  Can't the .Net designers redefine the word when applied to their own product?  I say YES, they can.

    My bottom line:  It is easy to say "Let's just have one method and be done with it" if you are not fully aware of every implication.

    P. S.:  I did not read this entire thread, so I am unsure if it has been pointed out or not.  The destructor of a managed object cannot dispose of managed resources because "it is too late for that".  Dispose() on the other hand, can do so freely.  Now, how do you minimize code duplication between the destructor and Dispose()?  Answer:  Dispose(bool).  The way I see it, the technique is as good as it can get.
    MCP
    Wednesday, September 9, 2009 8:36 PM
  • The main reason you have the parameterized version is for that little "if" statement that resides within it.  If you're finalizing an object, there's no telling whether the objects that hold this particular object as root have been removed from memory or not.  If they have, you can get a null reference exception.  If they haven't, you're fine.  That's the whole reason for passing in "true" from Dispose(), and "false" from the finalizer. 

    If you can find a simpler way to handle this in C#, let me know... I don't think you can, however.  This is about as simple as it gets.
    Coding Light - Illuminated Ideas and Algorithms in Software
    Coding Light WikiLinkedInForumsBrowser
    Wednesday, September 9, 2009 8:42 PM
    Moderator
  • I'm not sure what you are going on about.  But it already works this way.  Memory has nothing to do with finalization or IDisposable.
    Hans Passant.
    Wednesday, September 9, 2009 8:55 PM
    Moderator
  • Just as a side note... I heard someone spent months converting the .Net framework to use reference counting, which then allowed for deterministic finalisation/destrution of objects. I think this guy was an old VB6 programmer, and like you was convinced the .Net team had made a mistake in taking out deterministic finalisation and implementing dispose etc.

    In any case, the end result was the converted framework was so slow it wasn't useful and he threw the work away (I think, my memory is a little hazy). I think this was discussed in a .Net Rocks episode months ago, possibly even last year.

    Perhaps they (the .Net team) could have done something better if they'd done it at the beginning, but I'm sure they had their reasons for not doing so. Regardless, it is too late for them to change it now, so the discussion is kind of moot.
    Wednesday, September 9, 2009 9:08 PM
  • That was Chris Sells and Cristopher Tavares.  Chris Sells is not just some old VB6 programmer, he was a gifted C++ programmer and an author of many significant books.  He too, like the OP, was convinced that explicit memory management was superior to garbage collection.  They got a grant from Microsoft to prove the point.  Well, they tried for a long time and had to give up.  Their findings are available here.

    Another famous case was Raymond Chang's Chinese dictionary.   Rico Mariani wrote a managed version of it that significantly out-performed Chang's C++ version.   Chang then went on a mission to beat that result.  It took him 6 versions and a complete overhaul of the standard memory management in C++ to get there.

    That pretty much settled it, nobody has tried to prove that there is something wrong with the .NET garbage collector since then.  Well, until today perhaps.  I'll wait for the proof though.

    Hans Passant.
    Thursday, September 10, 2009 10:45 AM
    Moderator
  • Hi Hans,

    My apologies to Mr Sells and Mr Tavares, I didn't mean to imply they were 'just' anything (I guess I mis-spoke in my post)... clearly anyone who can do what they did has a significant amount of 'smarts' even if the end result wasn't successful in terms of performance... I was merely pointing out, as you did, that this proves the memory management in .Net is actually pretty good.

    Certainly that's food for thought for the OP.

    ... and thanks for correcting me and providing the extra detail on the Chinese dictionary.
    Thursday, September 10, 2009 9:21 PM
  • I think that some form of opt-in ordered finalization would be nice. That would make it possible to have managed "shutdown code" in the finalizer with a guarantee that it'll be run (as opposed to IDisposable). However, you still have the problem that finalization is not deterministic.

    BTW, "just don't worry about it" is a quick and easy way to a very buggy program. Ignoring IDisposable is a bad idea. Just a couple weeks ago, I spent half a day debugging a mysterious failure in a co-worker's code. The program was big enough that it had several non-IDisposable objects that should have been IDisposable. The fix - and I burn with shame that we didn't have time to fix it correctly - was to do a loop of Collect and WaitForPendingFinalizers at a particular point in the code.

    Of course, the problem lay dormant for months after release, until it ran on just the right database. In the field. Those kinds of fixes are expensive. Hence the quick hack. Which is now in our code base - commented, of course, but it's still a hack because no one has the time to fix it properly. Inspired by these events, I wrote another blog post telling people how to implement IDisposable.

    Oh, and the only reason that reference counting is less performant is because they were forced to have reference-counting garbage collection; in reference-counted languages (e.g., C++/Boost's shared_ptr), it's faster than garbage collection, but then that introduces the infamous circular reference problem (patched but not perfectly fixed by weak_ptr).

    For some designs, deterministic, reference-counted managed shutdown code is a necessity. Of course, reference counting is used by SafeHandle, the underlying wrapper for 95% of unmanaged resources. Also, many MVVM designs find themselves forced to introduce a layer of reference counting over the garbage collector when non-trivial MVVM apps are implemented.

          -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
    MSBuild user? Try out the DynamicExecute task in the MSBuild Extension Pack source; it's currently in Beta so get your comments in!
    Friday, September 11, 2009 1:42 AM