Profilo di StevenDevelopmentalBlogElenchi Strumenti Guida

Blog


06 gennaio

Object Finalization : IDisposable, Dispose(), Destructors and Garbage Collection

I apologise for the long title, but I wanted to be clear about what this post will cover. I was recently updating some of Readify’s courseware and was looking at our Garbage Collection section; that prompted this post.

Concepts

First lets define 2 concepts: Dispose() method and the Finalizer. In C#, the Finalizer is known as the ‘Destructor’ and is a special method that follows this syntax:

public class MyClass {
   public MyClass() { ..constructor logic.. }
   ~MyClass() { ..destructor logic.. }
}

As you can see, a destructor follows the constructor logic, but has a tilda ~ in front. In VB.Net there is no equivalent, however you can override the Object.Finalize method instead (which incidentally you can’t do in C#). So that is essentially the direct equivalent. Incidentally, because every class inherits from object, your class already has a Finalizer, it just doesn’t do anything until you implement it yourself.

Dispose() is a method that you define. There is an interface called IDisposable which will force you to implement this method. If you don’t define it, your class doesn’t have it.

What Are They Used For

Both the Finalizer and the Dispose() method are used for cleaning up your objects. This might be closing database connections, letting go of a disk file, or closing a stream from your serial port. It also means releasing handles to unmanaged resources such as pointers and COM artefacts.

The Finalizer (destructor) is called when the garbage collector cleans up your object. But garbage collectors are unpredictable: they will clean up your object whenever they feel like it. This is known as ‘non-deterministic finalization’ because we can’t determine when the object will be cleaned up. Also, the destructor is not a proper method so you cannot call it from your code.

The Dispose() method is just that: a method that you can call whenever you like. This makes it ‘deterministic’ in that we can determine when the cleanup will occur: when we call the method. By implementing IDisposable we are also telling other artefacts that we have that special cleanup method and can be called. A good example of that is the “using” statement. Here is an example:

using (StreamReader sr = new StreamReader(“C:\test.txt”)) {
  string s = sr.ReadToEnd();
}

In the above example a StreamReader object is created and utilised. The ‘using’ statement ensures that the Dispose() method is called at the closing bracket. In fact, it will do this even if an exception occurs within the ‘using’ scope. However, you can only use this syntax on classes that implement IDisposable.

Which Do I Use

If you have nothing to cleanup (and your sub-classes or base class have nothing to cleanup) then you don’t need to worry about either. However when you have resources to cleanup, then you should really implement both. The reason for this is simple: Provide Dispose() method for deterministic finalization, and provide a destructor in case consumers don’t call the Dispose() method.

And for those of you (like me) who hate to waste white space on duplicate code, you can call the Dispose() method from your finalizer, however it is best to consider some Microsoft recommended best practises when doing so.

Pattern For Finalization

This pattern is based on this article by Joe Duffy. The following example illustrates the pattern best:

private bool isDisposed = false;
public void Dispose()
{
            Dispose(true);
            GC.SuppressFinalize(this);
}

private void Dispose(bool disposing)
{
            if (isDisposed) return;
            // General cleanup logic here
            if (disposing)
            {
                        // Deterministic only cleanup
            }
            else
            {
                        // Finalizer only cleanup
            }
            isDisposed = true;
}

~MyClass()
{
            Dispose(false);
}

As you can see, there is a bool switch to determine if the Dispose logic applies to finalization or not. This pattern has some advantages when it comes to inheritance. For example, overriding the Dispose(bool disposing) method in a sub-class lets you customise how finalization will occur, without needing to provide the destructor or Dispose() methods (since via inheritance your class already has those methods and implements IDisposable). Also, we track if the object has already been disposed so that we don’t do it again. Finally, the call to GC.SuppressFinalze(this) means that since the object has been properly disposed, there is no need to call the destructor (ie. suppress finalization of this object). Make sure that call goes after the call to Dispose(true) : this ensures that if the Dispose method fails (raises exception) that the destructor will still execute again later and reattempt to cleanup the object.

Commenti (2)

Attendere...
Il commento immesso è troppo lungo. Immetti un commento più breve.
Immissione non effettuata. Riprova.
Impossibile aggiungere il commento al momento. Riprova più tardi.
Per aggiungere un commento è necessaria l'autorizzazione di un genitore. Chiedi autorizzazione
I tuoi genitori hanno disattivato i commenti.
Impossibile eliminare il commento al momento. Riprova più tardi.
Hai raggiunto il numero massimo di commenti pubblicabili giornalmente. Riprova tra 24 ore.
Impossibile lasciare commenti. La funzionalità è stata disattivata perché i sistemi hanno rilevato una possibile attività di spamming dal tuo account. Se ritieni che il tuo account è stato disattivato per errore, contatta il supporto tecnico di Windows Live.
Esegui il seguente controllo di protezione per completare la pubblicazione del commento.
I caratteri digitati nel controllo di protezione devono corrispondere ai caratteri dell'immagine o della riproduzione audio.

Per aggiungere un commento, accedi con il tuo Windows Live ID (se utilizzi Hotmail, Messenger o Xbox LIVE possiedi già un Windows Live ID). Accedi


Non hai ancora un Windows Live ID? Registrati

Steven Nagyha scritto:
Great point about the finalization queue! And yes, that article by Joe Duffy covers a pattern like what you have specified for Dispose(bool disposing). Its a long article but is a great read!
20 Gen.
Miguel Maderoha scritto:
Steven, great explanation. I will try to use thsi a resource when I need to point someone to this topic. Other posts I've read are either to complex or too simplistic... However I'd like to mention an important point.

Sometimes it's important to implemente IDisposable, but not a destructor. Destructors should be used only for unmanaged resources and dispose for both.

Why we shouldn't implemente the destructor?
The problem is that if you have a destructor even when you don't have unmanaged resources the GC will take longer to release the memory since it will send your object to the finialization queue.



When would we need a Dispose alone?
To avoid memory leaks. Lets say object A is subscribed to an event on objects B. If we don't unsubscribe from object B's event, object A will remain in memory until object B is no longer referenced.




I'll also change a bit the code in the Dispose method to reflect this. I don't think the else block is needed.

private void Dispose(bool disposing)
{
if (isDisposed) return;
// General cleanup logic here
if (disposing)
{
// Unsubscribe to events, set large objects/dependecies to null
}
// Dispose all umanaged resources, release handlers, etc.
isDisposed = true;
}



11 Gen.

Riferimenti

L'URL di riferimento per questo intervento è:
http://stevennagy.spaces.live.com/blog/cns!B2EFDBF0964586B3!329.trak
Blog che fanno riferimento a questo intervento
  • Nessuno