Peter said:
This implies you can't count on either the destructor OR the finalizer
from ever being called. What you said seems to imply one of these WILL be
called. Which is true?
If GC is performed, it guarantees to call the finalizer. However, garbage
collection is not guaranteed to be done. Garbage is collected when the
system thinks it is necessary (for example, it runs out of memory).
However, when the application exits, it is not guaranteed to be called,
because the OS cleans up after each application anyway. You can try to
call GC::Collect manually.
The destructor is always guaranteed to be called when the object is
instantiated with the stack semantics.
Would the following be accurate? If I create a ref class, I should only
create a destructor for it if I plan to delete some instances of it
manually on occasion [...] or if I plan to create some instances in stack
semantic form
To the best of my knowledge, if you allocate only managed memory, the GC
will take care of that, so it's not required that you call the destructor.
You must absolutely create a destructor and ensure deterministic cleanup
if you have unmanaged objects or resources inside your ref class.
Generally speaking, if an object inherits from IDisposable (defines a
destructor, in C++/CLI terms), at the minimum you're encouraged to call
the destructor. There are cases when you must call the destructor in a
deterministic manner, because you can't rely on GC. That decision has to
be made on a case-by-case basis.
That is, if all class instances will be deleted via the GC and are never
created in stack semantic form, then the destructor is just wasted code
taking up space.
No. You're encouraged to use the destructor if the class has one. The
finalizer is just a safety net, the last resort. If you could guarantee
deterministic destruction, there would be no need for a finalizer.
On the other side, if I can guarantee the instances will always be stack
semantic in form or they will always be manually deleted, then the
finalizer is wasted code taking up space.
I don't think there's too much overhead involved there. To prevent double
typing, the destructor and the finalizer can share code. You can write a
function and call that from both the d'tor and the finalizer. You can bet
that an object won't be destroyed both ways, but you have no idea how your
objects will be used. Ideally they're Disposed, sometimes they're
finalized, you really need to be prepared for both cases.
Wow! So in the effort to make things more simple and automitized we have
added a new layer of complication: the finalizer!
Look, there are cases where an automatic garbage collection works well,
and there are cases when it doesn't. There are cases when all you want is
automatic deterministic cleanup. There is such a thing, and one possible
implementation is the reference counted smart pointer (boost::shared_ptr).
It works as long as the language has stack semantics. Unfortunately .NET
doesn't always have it.
C++/CLI and C# have something that is a half-baked stack syntax. You can
create a local variable with deterministic destruction. In C# it's done
with the "using" keyword. However, stack semantics don't work well with
the current .NET collections. In a perfect world, value types would have
constructors and destructors too, so you could use stack semantics within
containers.
I'm (still) looking for a solution where I can guarantee deterministic
destruction with collections, something like List<shared_ptr<RefClass>>,
which would be equivalent to the native vector<shared_ptr<Class> >. I
don't see how it's going to happen, because I can't declare a destructor
for a value type. I'm not sure if STL.NET will bring anything to the
rescue.
Those who program in 100% managed environment probably won't care too much
about it, but many of my managed classes contain lots of unmanaged
objects, so I don't have a choice but to follow deterministic destruction
practices. Right now I'm in a situation where I was before
boost::shared_ptr was released... a lot of pain and insecurity.
Again, if you don't wrap unmanaged code, but work in a fully managed
environment, this issue is not nearly as pressing.
Tom