.NET Finalize - vs. - Destructor - vs. - Dispose



  • .NET has an automatic garbage collector for heap memory. For the .NET programmer, it's only necessary to worry about the garbage collection of so-called unmanaged resources: open files, database connections, etc.

    And yet, .NET somehow manages to turn this one little corner of the language - the deallocation of unmanaged objects -  into a confusing chore that's full of opportunities to screw up. So far as I can tell:

    1) A class that uses unmanaged resources should provide a Dispose() method to free them

    2) The consumers of such a class should call said Dispose() method

    3) The Dispose() method should call GC.SupressFinalize() (which lets the garbage collector know that the consumer of the class actually remembered to call Dispose() )

    4) Dispose() may get called multiple times; the class should deal with this gracefully.

    5) This Dispose() method should call base.Dispose() to ensure reclamation of unmanaged resources instantiated by the parent class.

    6) The class should also provide a Finalize() method; this acts as a sort of emergency backstop, which gets called by the automatic garbage collector when the class's managed resources get cleaned up. This Finalize() method needs to clean up the unmanaged resources as well

    Beyond item 2, this list seems asinine.

    Items 3 and 4 basically ask me to do .NET's work for it. Why can .NET not keep track of whether an object's unmanaged resources have been reclaimed? Why must I "tell".NET when this happens, and why can .NET not ensure that this happens only once?

    Item 5 imposes a requirement on the class designer that was not present even in C++. C++ destructors get called for the entire inheritance chain of a class automatically, when "delete" is called.

    Item 6 asks that the class designer write yet another (back-up) method that cleans up my unmanaged resources. Again, is there any reason .NET cannot track whether Dispose() (the other method I already wrote to clean up my unmanaged resources) has been called, and call it on an emergency basis?

    Even item 2 strikes me as a bit ill-conceived. I understand the theory behind it: it's acceptable to let allocated blocks linger on the managed heap, at least until memory becomes an issue. But open files, database connections, etc. cannot be dealt with in such cavalier fashion. They must be reclaimed (i.e. closed) on a timely basis.

    That's the theory, in any case. Personally, I think that requirement #2 relates back to the decision to use a non-deterministic garbage collector. Under a deterministic system like what C++ has, the rule for unmanaged resources could be as simple as "Clean up any unmanaged resources in a Dispose() method. .NET will call this automatically when the last reference to the instance falls out of scope." The lifetime of, for example, a database connection would end up having to be put into a pair of curly braces, e.g. a method, but that's arguably a good thing.

    Beyond this long list of easy-to-forget rules, there are some other annoying little quirks associated with this part of .NET:

    A) Finalize() methods are called "Destructors" in C# and use the ~classname() syntax... even though they're much different from C++ destructors.

    B) Dispose() is the sole member of an IDisposable interface, but you don't need to explictly implement the interface. Just make the method... .NET will find it!

    C) The C# language has keywords that are defined in terms of IDisposable, e.g. "using." But isn't IDisposable basically a data type written in C#? IDisposable is supposed to be a data type declared using the C# keywords. The keywords shouldn't be defined in terms of it... that's backwards. A similar situation exists for "for each"... and IEnumerable.

    D) Dispose() can also be called Close() if that makes more sense... although I'm not sure how this fits in with the idea of calling base.Dispose(). What if my base class implements Close() instead? (I guess this is a result of design-by-committee).

    Has anyone else noticed these things? Quirk "C" in particular bugs me!


  • ♿ (Parody)

    A lot of this really comes down to the difference between using non-deterministic instead of deterministic GC. I'm sure there are a lot of arguments one way or another on that topic, but it seems to me in a 99%/1% managed/unmanaged platform, non-deterministic makes sense. But that aside, Dispose() becomes inherently necessary in a non-determinisitc GC scheme if you want "easy" and explicit clean-up of certain operations.

    In .NET, IDisposable/Dispose() is implemented as a user-control/initiated concept, and there's no special "behind the scenes" magic that takes place.This also means that it follows the regular rules of .NET coding, and doesn't have any "special" treatement (unlike, say ThreadAbortExceptions).

    @bridget99 said:

    3) The Dispose() method should call GC.SupressFinalize() (which lets the garbage collector know that the consumer of the class actually remembered to call Dispose() )

    This goes back to the "Finalize as a last resort" rule; Finalize is not guarnteed to be called, and certainly not be called at any "known" time. Without this, we'd have to have special rules in place that allow the CLR to track when an method named Dispose is called.

    @bridget99 said:

    4) Dispose() may get called multiple times; the class should deal with this gracefully.

    Only by user code or unexpected situations. Because Dispose is a consumer concept, anyone could be an idiot and try to "super dispose" of something like "someObj.Dispose(); someObj.Dispose(); someObj.Dispose()." This guideline is an FYI.

    @bridget99 said:

    5) This Dispose() method should call base.Dispose() to ensure reclamation of unmanaged resources instantiated by the parent class.

    This goes back to Dispose implemented as a user-driven/controlled concept. Dispose() is just a regular method, and regular methods don't automatically call their parent methods.

     

    @bridget99 said:

    C) The C# language has keywords that are defined in terms of IDisposable, e.g. "using." But isn't IDisposable basically a data type written in C#? IDisposable is supposed to be a data type declared using the C# keywords. The keywords shouldn't be defined in terms of it... that's backwards. A similar situation exists for "for each"... and IEnumerable.

    "using" is syntactical/compiler sugar. "using(A a = new A()) { ... }" is semantically the same as "{A a = new A(); try { ... } finally { a.Dispose(); } }".

     



  • @bridget99 said:

    A) Finalize() methods are called "Destructors" in C# and use the ~classname() syntax... even though they're much different from C++ destructors.

    Actually, they're called "finalizers" now, because the previous name was indeed confusing. See Ecma 334, Section 17.12.


Log in to reply