Fog Creek Software
Discussion Board




.NET destructors - Is this important?

Joel has talked about destructors not firing when an object variable goes out of scope - and he says that this is a "step backwards".

Do people think that this is a step backwards, and if so, do the advantages gained by using a separate garbage collection routine make up for this?

Mark Alexander Bertenshaw
Wednesday, April 24, 2002

There are many advantages to an environment where memory is managed for you.  The only case, that I really think about anyway, where the you get burned by non-deterministic finalization is the situation where an object is handling a precious resource like a database connection.  Normally, in the COM world, when the last client disconnects from the object (RefCount = 0) you'll write code to release resources like db connections, file handles, etc.  This code is executed in a very predictable manner. 

Regarding .NET, MS provides a method called Finalize which allows the dev to write this clean-up code, but it isn't nearly as predictable as the scenario I previously described involving COM.  Basically, the GC runs on a low priority thread looking for objects that can't be reached anymore.  If no Finalizer is present, the memory is reclaimed.  If a Finalizer is present, it's basically flagged and ANOTHER thread comes by (hopefully) soon and fires the Finalizer.
Then the object is GC'd normally.  The docs are VERY conservative on this topic.  They also indicate that there is no guarantee the Finalizer will fire (!)

(This description of the GC process the information provided here is provided "as is" without warranty of any kind. Rick disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. in no event shall Rick be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages, even Rick has been advised of the possibility of such damages. some states do not allow the exclusion or limitation of liability for consequential or incidental damages so the foregoing limitation may not apply.)

MS recommends you implement the Dispose pattern, which basically means you implement a method that you'll explicitly call to do your clean-up.  But that takes away any advantage we had with deterministic finalization.

You can always implement you're own reference counting!

;-)

(The information provided here is provided "as is" without warranty of any kind. Rick disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. in no event shall Rick be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages, even Rick has been advised of the possibility of such damages. some states do not allow the exclusion or limitation of liability for consequential or incidental damages so the foregoing limitation may not apply.)

Rick
Wednesday, April 24, 2002

Re. Finalize "event".  I gather from reading articles on .NET that it is possible that Finalize might not get called at all - which is why they recommend you put all your clear up code in the Dispose method (assuming you implemented IDisposeable).  And I believe that if you implement Finalize, you can get into some interest situations, since the garbage collector in this case has to put the object in a special "Finalize" queue, where objects wait to have their Finalize methods called.

Personally, I think that I will really miss COM type object destructors, since I am a VB programmer, and have made great use of this feature in a whole lot of my code, since it allows a lot of elegant coding.  Having no need for a Terminate() type method means that I don't have to remember calling it, thus leading to fewer potential bugs. 

In general, I am really in two minds about VB.NET initiative.  Whilst I really love the new features, and the light-weight objects which come with .NET, I think that it lacks something in the VB department.  And also, they lost the opportunity to add some interesting features.  I mean - why not parameterised Gosubs as a language feature?  All the stack friendly advantages of local variables, with the strong type checking of procedures!

Mark Alexander Bertenshaw
Wednesday, April 24, 2002

>> I mean - why not parameterised Gosubs as a language
>> feature? All the stack friendly advantages of local
>> variables, with the strong type checking of procedures!

You're not serious.....

Rick
Wednesday, April 24, 2002

It's a similar situation (if not exactly the same) in Java.  You never know whether Finalize will be called.  So, I just don't use it.  I have yet to have a real need for a destructor, although my experience is intermediate at best.  I guess it works well for most situations, adding to the safety net of GC.

Patrick Lioi
Wednesday, April 24, 2002

Re Patrick's comment about Java finalization. It's also not a good idea to rely on this in wrapper objects to clean up the wrapped objects. From my experimentation, (http://www.zanthan.com/itymbi/archives/000345.html if you're interested) which matches what you think would happen, the wrapped object is finalized before the wrapper is. So, for example, if you wrap an SQLConnection and expect to be able to close it in the finalize method you're going to cause an SQL exception because by the time your code is executed the underlying socket connection to the db is already closed. 

Alex Moffat
Wednesday, April 24, 2002

Actually in Java the use of Finalizers is strongly discouraged as most of the GC optimisations cannot be used in a Finalize method is present - in case of object resurrection, etc.

It seems that having a GC makes memory management easier, at the expense of making the management of all other resources more painful. And making you application have non-deterministic run-time behaviour.

Whether this is a good thing or not depends on whether you consider memory management to be a significant problem.

anon
Wednesday, April 24, 2002

> for example, if you wrap an SQLConnection and expect to
> be able to close it in the finalize method you're going to
> cause an SQL exception because by the time your code is
> executed the underlying socket connection to the db is
> already closed.

I think close() functions should be idempotent. close() means that the programmer wants that SQLConnection or whatever object to be destroyed. If it was already closed, then who cares? Library authors should assume the people calling the library are dummies and program defensively (within reason).  ;-)

I will miss destructors. I don't think Java programmers are bothered by Java's lack of destructors as much as C++ programmers are because Java does not allow objects to be allocated on the stack.

Banana Fred
Wednesday, April 24, 2002

This topic was flogged to death over on the DevelopMentor .NET mailing list (http://discuss.develop.com) just after .NET first hit the streets.

Part of what came out of this dicussion was two patterns on how to handle object cleanup.

The first thing to do is always implement IDisposable on your objects if they need cleanup. This gives you a standard place & name to give your Close method (I forget what they actually called the method, though).

Then, use one of the two forms in your code:

MyResource res = new MyResource( "Something expensive");

try
{

    // do stuff with res
}
finally
{
    res.Close();
}

The try/finally ensures that res gets cleaned up even in the presence of exceptions.

The other option is the new using form added to C# (so this won't work in other .NET languages).

using( MyResource res = new MyResource(...) )
{
    // Use res
}

At the end of the using block, the compiler automatically calls res.close().

I may have a couple of details wrong, but this is the general idea.

It's a little less convienient than C++, yes, but it's still a lot less work than tracking down cycles & bugs in reference counts!

Chris Tavares
Wednesday, April 24, 2002

If Java allowed stack-based objects, then these hacks of using try/catch/close or using() would be unnecessary. Those verbous idioms are simply reinventing C++ destructors. sigh..

btw, does C# allow stack-based objects?

Banana Fred
Wednesday, April 24, 2002

My argument against the Close() idea is that it is really a bad idea to have an object in an invalid state.  If it is in an invalid state, it ought to be literally unusable, or not even reachable.  And also, I generally like to think that if there is a Close() method, there should also be an Open() method.  But that's just the aestheticist in me, I suppose.

As for the parameterised GoSub - I am not 100% joking.  I recently wrote some reasonably complicated parsing code.  Inside it, there was several places where the same seven lines of code was being run, so I did the obvious thing, and moved those lines to a separate procedure.  But then, I realised I was passing about seven parameters, which was a real pain.  It occurred to me then, that I could potentially use a GoSub, and thus save the overhead of a procedure call.  However, in native code compiled VB, GoSubs are bizarrely a lot slower than procedure calls, and anyway I'm not very enamoured with the syntax.  What would be cool is if I could gain the specified interface benefits of a procedure, but also be able to have the information hiding and stackless subroutine call of GoSub.

Mark Alexander Bertenshaw
Thursday, April 25, 2002

Use Delphi:

function TMyObject.IsZero(Param: Integer): Boolean;

  procedure IsArgZero: Boolean;
  begin
    Result := Param = 0;
  end;

begin
  Result := IsArgZero;
end;

:-)

Frederik Slijkerman
Thursday, April 25, 2002

> btw, does C# allow stack-based objects?

No, not really. You can put structs on the stack, but they aren't allowed to have finalizers, so you're back at square one.

Something to consider is looking back at Lisp and Smalltalk. Both of these communities have been dealing with these issues for what, 30 years now? What do *they* do?

Chris Tavares
Thursday, April 25, 2002

Mark wrote:
Inside it, there was several places where the same seven lines of code was being run, so I did the obvious thing, and moved those lines to a separate procedure. But then, I realised I was passing about seven parameters, which was a real pain. It occurred to me then, that I could potentially use a GoSub, and thus save the overhead of a procedure call.



Sounds like you want FORTH.NET!

Adam Vandenberg
Friday, April 26, 2002

I just wanted to point out a fairly common pattern used in GC'ed languages to deal with this issue. It uses closures--object-oriented equivalents to function pointers--which in Java are typically implemented as anonymous implementations of some standard interface.

The idea is that for each resource you create a class that has a "withDo" method, which accepts a closure. The method acquires the resource, runs the closure passing it the resource, and ensures that the resource is released after the closure returns (either normally or by throwing an exception).

There's an example in Java here:

http://www.c2.com/cgi/wiki?ReleasingResourcesInJava

It sounds like the C# "using" statement is the same idea baked into the language directly.

Tim Moore
Friday, April 26, 2002

*  Recent Topics

*  Fog Creek Home