Myths about finalizers, part two
ericlippert.com
ericlippert.com
I tend to think that using finalizers is almost always a bad idea. They're just too complicated: there's no way you can reason about them effectively. This long list of assumptions you can't make about finalizers is just an example of this: if you have to keep all of this in mind you're forgetting something else. Luckily there are alternatives.
It is therefore very difficult indeed to write a correct
finalizer, and the best advice I can give you is to not try.
Since the initial release, .NET has come up with additional mechanisms to try and minimize the number of situations where you would need to implement a finalizer. At this point I find that it's always possible to avoid one. When I think I need one, I ask myself the following question: "Am I hacking on the core framework?" If the answer is no, then I probably shouldn't be writing a finalizer.AKA say you have a resource (like a file handle) that is being wrapped by a class. In C++ the constructor would open the file, and the destructor would close it. In a GC'ed environment because you can't determine when the instance is going to get cleaned up the programmer ends up calling a .close() or Dispose() method instead of the destructor. So the finalizer ends up just being a little piece of code that says, did the user remember to call close? No? Complain loudly enough someone will fix it.
"it is possible that the finalizer and constructor are running concurrently."
AKA, if you have an object that needs to be disposed, and the finalizer is running concurrently with the constructor then that is because you forgot to call the Dispose() method and any complaining it does is valid.
EDIT: Although, thinking about it more, the bigger problem is the partial construction case, where you need to be really careful how you set/unset the flag used to determine if the dispose method is called. But if something is bad enough to result in the construction phase failing in some really bad way, then there should be noise about that too. I would hope!
The problem with Dispose is that it's infectious. If one of your instance variables needs to be Dispose'd, then you do too. This is all sorts of bad: it violates LSP, leaks implementation details, adds compatibility constraints, etc.
The second line is why GC is a lot of hype... eventually you need a pthread_mutex_t and GC programming becomes just as onerous as reference counting.
BTW: Even in C++ though, I wouldn't use a pthread mutex directly, or in a wrapper class if I haven't banned exceptions. The "correct" way is to create a resource wrapper class without public lock/unlock calls and then a second RAII lock class that can lock/unlock the wrapper. Then if an exception happens while holding a lock you at least have a chance of properly unlocking it, or noticing that it wasn't unlocked.
1. Use a SafeHandle or CriticalHandle
2. Let the finalizers for the resources you're holding take care of themselves.
#2 because if your resources already have their own finalizers (e.g., streams, db connections), then implementing your own is like a belt and suspenders. Except that whichever one you added 2nd is probably broken and makes everything strictly worse.In your case 2, unless you try to force the finalizer to run (he has a collect/waitforpendingfinalizers sequence in one of his examples) this potentially leaves the resource consumed for an indeterminate amount of time.
Its also not clear to me that the *Handle routines guarantee correct behavior with processes running outside of the CLR. I just looked at the docs and it reenforced my understanding that they seem to be strictly designed to protect .net applications from shooting themselves in the foot. I'm not a .net/CLR expert so take that for what its worth.
I've made this mistake, and the way it bit me was in a system where I was spinning up and disposing a lot of event subscriptions. My UI slowed to a crawl. It turned out that the finalizer queue is pretty low priority, so event subscriptions weren’t getting disposed by the finalizer until there was time to do so. As a result, extra event handlers were being notified (and doing nothing because they in turn called other events which were no longer subscribed). This wouldn’t have been a problem to find and fix, except that I had written the finalizer six months before and one of our junior devs had written code that depended on Dispose() being called by the finalizer instead of explicitly. The result was that profiling showed a lot of time being spent on meaningless code that wasn’t actually related to the problem. It took almost a week to figure out the issue. I thought when I was writing that finalizer that I was writing fail-safe code, but in reality I had just made the failure harder to trace.
In short, the way to ensure that Dispose() has been called is to call Dispose() at the appropriate time. There’s no shortcut, you have to just manage the lifetime of your objects correctly.
AKA, using it as a debugging aid.
> Finalizers are a Ouija board, permitting dead objects to operate "from beyond the grave" and affect live objects. As a result, when finalizers are involved, there is a lot of creepy spooky juju going on, and you need to tread very carefully, or your soul will become cursed.
If we can't use scope locks, we should use open()/close() -style semantics.
I found myself in such a situation. Books like Advanced .NET Debugging [1] will help you regain your sanity.
[1] http://www.amazon.com/Advanced-NET-Debugging-Mario-Hewardt/d...