Here is an example about which I am uncertain:
public class SomeClass : IDisposable {
~SomeClass() {
Dispose(false);
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
private bool _disposed;
protected virtual void Dispose(bool disposing) {
if (!_disposed) {
if (disposing) {
// TODO: Release any managed resources here...
}
// ?! Is it safe to enumerate the dictionary here ?!
foreach (var resource in _resources.Values)
ReleaseBuffer(resource);
_resources = null;
_disposed = true;
}
}
private Dictionary<string, IntPtr> _resources;
...
}
Will it be safe to enumerate the managed dictionary in order to release the unmanaged resources?
Is availability of the dictionary uncertain since the order in which finalizers are invoked is not defined?
Here is a quote taken from the MSDN which I find confusing [1]:
- The finalizers of two objects are not guaranteed to run in any specific order, even if one object refers to the other. That is, if Object A has a reference to Object B and both have finalizers, Object B might have already been finalized when the finalizer of Object A starts.
Rather than having a dictionary of unmanaged resources, I would suggest having a dictionary of independent wrapper objects, each of which is responsible for guarding one unmanaged resource. If the object holding the dictionary is abandoned and no other references exist to the wrapper objects, all of the wrapper objects will get finalized without needing to involve the dictionary itself in the process. Using such an approach will make it easier to sanely handle cases in which an exception occurs during object construction, and at least somewhat-sanely deal with situations where an object finds itself resurrected between the time it has been enqueued for finalization and the time the finalizer runs [code generally can't be expected to run "correctly" in such cases, but should avoid corrupting the state of the rest of the system].
For example, code which uses a handle may acquire a lock during its use and, after use, check a "disposeObjectASAP" flag; if set, re-acquire the lock and dispose object. The finalizer itself should set the flag and then try to acquire the lock; if it successfully acquires the lock, it should dispose the object. If unable, the fact that it set the flag should imply that code which has the lock is destined to check the flag and clean up the object, so the finalizer doesn't have to. If the finalizer runs prematurely, it may release resources which another thread is going to need, causing actions on that other thread to fail, but the finalizer won't release resources while another thread is using them or disposing them, since releasing resources in those situations could cause massive system corruption.