Calling .Dispose() on a class that has a Finalizer
Asked Answered
C

1

6

According to Essential C# 6.0 you should:

AVOID calling Dispose() on owned objects that have a finalizer. Instead, rely on the finalization queue to clean up the instance.

  1. Could someone please elaborate on this as I'm not clear on what the point of Dispose is if we're not to call it from owned objects?
  2. Apart from Reflection, how would you figure out if the object has a Finalizer?
  3. How do you figure out when to call Close() / Close() + Dispose() other than searching the API documentation (if you have access to it and it exists) or Reflection? I see a lot of questions around the net for very specific types ( MemoryStream / Form / SqlConnection / etc ) but I'm looking more at "how to figure it out yourself".

According to the Dispose Pattern you should:

CONSIDER providing method Close(), in addition to the Dispose(), if close is standard terminology in the area. When doing so, it is important that you make the Close implementation identical to Dispose and consider implementing the IDisposable.Dispose method explicitly.

but there are times when you should call both like with Form, etc. Questions like "Close and Dispose - which to call?" get close but there's no defined approach apart from

As usual the answer is: it depends. Different classes implement IDisposable in different ways, and it's up to you to do the necessary research.

Edit: Here is the full guideline, I haven't asked for permission to reproduce but since it's a guideline (thereby assuming it's supposed to be freely shared public knowledge) and not some part of the actual training material, I'm hoping I'm not breaking any rules.

Guidelines
DO implement a finalizer method only on objects with resources that are scarce or expensive, even though finalization delays garbage collection.
DO implement IDisposable to support deterministic finalization on classes with finalizers.
DO implement a finalizer method on classes that implement IDisposable in case Dispose() is not invoked explicitly.
DO refactor a finalization method to call the same code as IDisposable, perhaps simply calling the Dispose() method.
DO NOT throw exceptions from finalizer methods.
DO call System.GC.SuppressFinalize() from Dispose() to avoid repeating resource cleanup and delaying garbage collection on an object.
DO ensure that Dispose() is idempotent (it should be possible to call Dispose() multiple times).
DO keep Dispose() simple, focusing on resource cleanup required by finalization.
AVOID calling Dispose () on owned objects that have a finalizer. Instead, rely on the finalization queue to clean up the instance.
AVOID referencing other objects that are not being finalized during finalization.
DO invoke a base class’s Dispose() method when overriding Dispose().
CONSIDER ensuring that an object becomes unusable after Dispose() is called. After an object has been disposed, methods other than Dispose() (which could potentially be called multiple times) should throw an ObjectDisposedException.
DO implement IDisposable on types that own disposable fields (or properties) and dispose of said instances.

Curculio answered 21/1, 2016 at 9:38 Comment(0)
B
6
  1. Could someone please elaborate on this as I'm not clear on what the point of Dispose is if we're not to call it from owned objects?

Without the full text of the book and its context (I don't have a copy of the book, nor may many other people reading your question), it's impossible to say for sure what they mean. But it's supposed to be a good book, and as such I have to assume that the text you're quoted is intended to pertain only to code in your own finalizer. I.e. of course you should dispose owned objects normally. In your Dispose() method.

It's about what to do if your object hasn't been disposed properly. And the answer there is to just clean up your own unmanaged resources.

Related to this is that now, with the advent (some time ago) of the SafeHandle class, you may not need a finalizer at all. Instead, wrap your own unmanaged resources in a SafeHandle subclass and let that class deal with finalization.

  1. Apart from Reflection, how would you figure out if the object has a Finalizer?

Apart from reflection, you'd be relying on the source code (if available), the documentation (if written), or simply the fact that the object implements IDisposable (i.e. make an assumption…it's not guaranteed, but there's a strong correlation between the two).

More to the point, note that since it is possible to correctly implement an object that implements IDisposable without using a finalizer (e.g. if you use SafeHandle, or if you implement IDisposable only so that you can deterministically clean up owned IDisposable objects), the presence of a finalizer is not guaranteed.

I think a better way to word the guidance would be "don't dispose objects in your finalizer". Rely on the fact that an IDisposable object should itself somehow deal with finalizing its own owned resources, and focus only on any unmanaged resources your own object owns directly.

  1. How do you figure out when to call Close() / Close() + Dispose() other than searching the API documentation (if you have access to it and it exists) or Reflection? I see a lot of questions around the net for very specific types ( MemoryStream / Form / SqlConnection / etc ) but I'm looking more at "how to figure it out yourself".

You can't. Not without inspecting the code carefully. That said…

You should never have to call both Close() and Dispose(). The two should always be equivalent, if the class is implemented correctly.

Of course, there's nothing in .NET that enforces that. So it's not possible to say for sure you wouldn't need to. But if you're dealing with a type that requires both, it was poorly written and may be broken in other ways as well. May be best to just avoid using that type altogether. :)

And of course, as you point out, the Form class in some cases requires you to call both Close() and Dispose() (in most cases, calling Close() is actually sufficient…it's only because of the weird way they implemented modal dialogs that you get the exception to the rule). But that's a very old API, designed before the full implications of the complexities of the IDisposable pattern were really fully understood. One hopes Microsoft wouldn't design that API the same way today if they had to do it again (and indeed, WPF doesn't have that same dichotomy).

Modern implementations should do a better job of following good conventions more uniformly.


Addendum:

I did a little browsing around. There are, of course, lots of articles about GC, finalization, IDisposable, finalizers, etc. on Stack Overflow, but I didn't see any that seemed to be directly equivalent to your question. This one seemed the closest though:

Which objects can I use in a finalizer method?

Others that might be useful additional reading:

When would dispose method not get called?
Why call Dispose()? Memory leak won't occur?
IDisposable and managed resources

And of course, the classic:
Proper use of the IDisposable interface

Breazeale answered 22/1, 2016 at 1:24 Comment(5)
The best non SO article about how to do proper resource disposal I have ever seen is "IDisposable: What Your Mother Never Told You About Resource Deallocation" written by Stephen ClearyCorner
That's a great answer Peter, I've added the complete guideline. After posting the guideline, rereading the guideline and your remark, it appears from my perspective that you are correct in saying that it looks like it's related to finalizers where I was under the impression it was relating to IDisposable.Dispose() which is why I was clearly confused. I like your answer to point 2 as well, some further research I can do there on SafeHandles. Regarding point 3, seems that pure experience and trial & error are pretty much the order of the day. My OCD's are thrilled.Curculio
@Curculio The article I linked to in my last comment goes in to how how SafeHandles work and how to correctly implement your own.Corner
@ScottChamberlain Thanks Scott, I have it open right now and also why I upvoted your comment :) I was going to come back to you once I've been through it, always appreciate any assistance!Curculio
Sorry I haven't finalised this question yet, it's been a rough few days, will get back just as soon as I have a few hours to spareCurculio

© 2022 - 2024 — McMap. All rights reserved.