Of course, it would be much better to avoid the finalizer altogether, and use SafeHandle
, as the modern idiom dictates. Then all of this stuff about finalizers becomes completely moot.
That said, the wisdom of doing so notwithstanding, it is perfectly safe to call GC.SuppressFinalize()
from the finalizer. The documentation for the method describes what the method does:
This method sets a bit in the object header of obj, which the runtime checks when calling finalizers.
The runtime may actually check this bit during a GC operation as well, which is when on finding an object unreachable, that object's finalizer would be put into the finalizer queue. If it's set at that point, finalizer doesn't even wind up in the queue.
Checking it again later, before calling the finalizer itself, also allows finalization of the object to be avoided, if it turns out that some other object's finalizer wound up disposing it even though the that object's finalizer was put in the finalization queue.
Both of these checks occur before the finalizer is called. Once the finalizer is called, the bit in the object has no purpose. Setting it is harmless, but won't accomplish anything.
As an aside: note that past implementations of .NET used Finalizer
and FReachable
queues. When an object was created, if it had a finalizer, it would be moved to the Finalizer
queue. Once the object was unreachable, it would be moved to the FReachable
queue for later finalization. Calling SuppressFinalize()
would remove the object from the Finalizer
queue. By the time the finalizer runs, the object is no longer in this queue, so the SuppressFinalize()
call would be a NOP, similarly harmless.
Now, that said, your question is broad: "…are there any bad, unwanted, or otherwise tangible correctness or performance effects?". Much of that is in the eye of the beholder. I would argue that a finalizer that calls GC.SuppressFinalize()
is incorrect. So, that would be a "tangible correctness effect" to me. I also find code that deviates from published, acknowledged standard patterns to be "unwanted". Without more specific criteria in the question to constrain it, the answer to that part of the question could be any of "yes", "no", "sometimes", etc.
There is in fact a duplicate question to yours, but no one's deigned to answer it: Calling GC.SuppressFinalize() from within a finalizer. I do find the thread of comments on point though, especially Eric Lippert's contributions:
Your supposition is that the unnecessary call to SuppressFinalize is the error in your plan. That's not the problem; the problem is the disposal of managed resources on the finalizer thread. Recall to your mind that finalizers run on their own thread, and that managed resources can be thread-affinitized, and now start to imagine the horrors that could result. Moreover: finalizers run in arbitrary order. A managed object disposed on the finalizer thread might have already been finalized; now you are possibly running finalization logic twice on one object; is it robust to that scenario? – Eric Lippert Mar 31 '16 at 21:58
1
Writing a correct finalizer is extraordinarily difficult and I recommend against you trying ever, ideally, but definitely hold off until you understand the pattern better. If you are not sufficiently scared yet, my series of articles on the subject might put more fear into you: ericlippert.com/2015/05/18/… – Eric Lippert Mar 31 '16 at 21:59
…
@Tom: The question is "I'm using the dispose pattern completely wrong; is this particular part of what I'm doing wrong?" No, the whole thing is wrong from the very first sentence. You don't use Dispose to dispose managed resources, and you certainly don't use a finalizer for that. That's the problem here. Is there anything wrong, per se, with calling SuppressFinalize from a finalizer? Well, it will work, but there should not be a situation in which that is the correct thing to do, so whether it works or not should be irrelevant. – Eric Lippert Jul 7 at 14:17
@Tom: Also, why do you call SuppressFinalize in the first place? Only because it is a performance optimization. But under what circumstances is it an optimization when called from the finalizer thread? Only when you've failed to make that optimization from the main thread! That's the place to do that optimization! – Eric Lippert Jul 7 at 14:24
IMHO, these comments bring the primary issue to a fine point: asking whether it's safe to call SuppressFinalize()
from the finalizer is the wrong question. If you've gotten as far as having to ask that question, the code is already wrong, and the answer to the question is probably not all that relevant. The right approach is to fix the code so you don't have to ask that question.
Finally, while not exactly the same issue, I think it's also worth pointing out that the usual guidance to call SuppressFinalize()
at the end of the Dispose()
method is probably incorrect. If called, it should be called at the beginning of the Dispose()
method. See Be Careful Where You Put GC.SuppressFinalize