Two questions about Dispose() and destructors in C#
Asked Answered
D

3

16

I have a question about how to use Dispose() and destructors. Reading some articles and the MSDN documentation, this seems to be the recommended way of implementing Dispose() and destructors.

But I have two questions about this implementation, that you can read below:

class Testing : IDisposable
{
    bool _disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed) // only dispose once!
        {
            if (disposing)
            {
                // Not in destructor, OK to reference other objects
            }
            // perform cleanup for this object
        }
        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);

        // tell the GC not to finalize
        GC.SuppressFinalize(this);
    }

    ~Testing()
    {
        Dispose(false);
    }
}

GC.SupressFinalize(this) on Dispose()

When the programmer uses using or calls Dispose() explicity, our class is calling to GC.SupressFinalize(this). My question here is:

  • What this exactly means? Will the object be collected but without calling the destructor?. I guess that the anwswer is yes since destructors are converted by the framework to a Finalize() call, but I'm not sure.

Finalizing without a Dispose() call

Suppose that the GC is going to clean our object but the programmer did not call Dispose()

  • Why don't we dispose resource at this point? In other words, why can't we free resources on destructor?
  • What code must be executed in the if inside, and what outside?

    if (!_disposed) // only dispose once!
    {
       if (disposing)
       {
           //What should I do here and why?
       }
       // And what here and why?
    }
    

Thanks in advance

Dowson answered 6/1, 2011 at 13:12 Comment(6)
AFAIK this pattern is outdated. For unmanaged resources you usually should use a SafeHandle and managed resources usually don't require a finalizer.Squeegee
@codein, you're right but is there a canonical write-up on MSDN somewhere?Tarlatan
@CodeInChaos: Could you provide some literature about it, please?Purlieu
Personally I subscribe to the school of thought that says that the only thing a (non critical) finalizer should do is notifying the programmer of a potential bug.Squeegee
I have no canonical reference at hand, but there are several blogs describing either the problems with normal finalization(in particular it doesn't always execute and is hard to code) or the advantages of safehandles/critical finalization blogs.msdn.com/b/bclteam/archive/2005/03/15/396335.aspx blogs.msdn.com/b/cbrumme/archive/2004/02/20/77460.aspx msdn.microsoft.com/en-us/library/fh21e17c(v=vs.90).aspxSqueegee
@HenkHolterman Its not on the MSDN, but it is written by Stephen Cleary "IDisposable: What Your Mother Never Told You About Resource Deallocation"Baccivorous
T
13

1. What does SuppressFinalize do?

It unregisters the object from the finalizer list, ie when the GC later collects the object it will ignore the presence of the destructor. This is a big gain in performance since the destructor would otherwise require the object's collection, and that of everything it references, to be delayed.

2. Why don't we dispose [managed] resource at this point? In other words, why can't we free [managed] resources on destructor?

You could, but it is certain to be pointless: the object you're in has become unreachable so all those owned managed resources are unreachable too. They will be Finalized and collected by the GC in the same run and calling Dispose() on them is unnecessary but not totally without risk or cost.

2a What code must be executed in the if inside, and what outside?

Inside the if(disposing), call _myField.Dispose()

In other words, Dispose of managed resources (objects with a Dispose)

Outside, call code to cleanup (close) unmanaged resources, like Win32API.Close(_myHandle).

Note that when you don't have unmanaged resources, as will usually be the case (look up SafeHandle), you don't need a destructor and therefore no SuppressFinalize.

And that makes that the complete (official) implementation of this pattern is only needed because of the possibility that Test is inherited from.
Note that the Dispose(bool) is protected. When you declare your class Testing to be sealed, it is totally safe and conforming to omit the ~Testing().

Tarlatan answered 6/1, 2011 at 13:21 Comment(13)
@Henk: After your edit about 2a: Than means that unmanaged resources are always cleaned even the programmer forgot call dispose?Purlieu
-1 That's incorrect. You can not dispose resources in the finaliser. The references may be invalid, so you can't touch them.Zoezoeller
@Guffa: A reference cannot become invalid as long as you hold it. Type-safety holds up all the way to the Finalize fase. It is just tricky: myField could already have been Disposed (but not collected!). Dispose() on a Disposed object should be safe.Tarlatan
@Guffa: I think Henk is rigth, because he means dispose managed objects inside the disposing block, and this is not the finalizer, is the Dispose() call made by the programmer.Purlieu
"Than means that unmanaged resources are always cleaned even the programmer forgot call dispose?" No, only if the finalizer happens to run. There are several scenarios where it doesn't run. In particular on forced app-domain unload. This causes problems in multi AppDomain applications.Squeegee
Henk is right but... it's worth emphasising again that by referencing a managed IDisposable resource in your finaliser you could be artificially keeping it alive longer than necessary. If you didn't reference it in the finaliser then the GC would be free to collect it (and call its fallback finaliser, if it has one) as soon as it's no longer required. There's no need to dispose of managed IDisposable resources in your finaliser: if those resources need any fallback finalisation then they should handle it themselves internally.Satsuma
@Daniel, "...always cleaned even the programmer forgot call dispose?" Usually yes, with some (rare) exceptions noted by CodeInChaos . But 'forgetting' to Dispose() is always (very) inefficient, and it could lead to failure.Tarlatan
@Daniel Peñalba: If he means that, he certainly expressed himself very badly, as he is answering the point about disposing from the finaliser. I don't see the point of explaining what you can do when disposing as an answer to what you can do when finalising.Zoezoeller
@Guffa: I was answering about an if() statement that is called in both the Finalizer and the Dispose() ... What/when was the core of the question, but maybe I read is as badly as I express myself.Tarlatan
@Henk Holterman: If you were, you truely express yourself badly. It clearly looks like you were answering the question "why can't we free resources on destructor?", as the answer follows the headline, and takes the form of a direct answer to it.Zoezoeller
@Guffa, right, you have to refer back to the question to know that is specifically ("at this point") about managed resources.Tarlatan
@Henk Holterman: You are missing the point completely. It's still a question about what you can do in the finaliser.Zoezoeller
@Guffa, Yes, and I answered that: You can but should not call Dispose() on an owned object. We seem to disagree about the can.Tarlatan
D
2

First part:

When GC.SupressFinalize(this) is called, GC is informed that the object has already freed its resources and it can be garbage-collected as any other object. And yes, finalization and "destructors" are the same thing in .NET.

Second part:

Finalization is done by a separate thread and we have no control on time and order of finalization, so we don't know whether any other objects are still available or are already finalized. For this reason, you can't reference other object outside the disposing block.

Dicentra answered 6/1, 2011 at 13:23 Comment(1)
FYI, after the system determines that some objects are eligible for finalization, all such objects, and all objects to which they hold direct or indirect references, will become "live" until such time as the finalizers actually run. If finalizable object X holds the only existing reference to finalizable object Y, and nothing holds a reference to X, then when X.Finalize() runs, object Y may or may not have had its Finalize() routine run, but object Y is guaranteed to still exist.Sedum
S
1

The vast majority of the time when an object which owns IDisposable resources is finalized, at least one of the following statements will apply to each of those resources:

  1. It has already been finalized, in which case no cleanup is necessary.
  2. Its finalizer hasn't yet run, but is scheduled to do so, in which case no cleanup is necessary.
  3. It can only be cleaned up within a particular thread (which isn't the finalizer thread), in which case the finalizer thread must not try to clean it up.
  4. It may still be in use by someone else, in which case the finalizer thread must not try to clean it up.

In some of the rare circumstances where none of the above apply, cleanup within the finalizer might be appropriate, but one shouldn't even consider it unless one has first examined the above four possibilities.

Sedum answered 19/11, 2015 at 0:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.