Implémentation des méthodes Finalize et Dispose pour nettoyer des ressources non managées

RemarqueRemarque

Pour plus d'informations sur finaliser et supprimer des ressources à l'aide de C++, consultezDestructors and Finalizers in Visual C++.

Les instances de classe encapsulent souvent un contrôle des ressources qui ne sont pas managées par le runtime, telles que les handles de fenêtre (HWND), les connexions de base de données et ainsi de suite. C'est pourquoi vous devez fournir un moyen implicite et explicite de libérer ces ressources. Pour fournir le contrôle implicite, implémentez le Finalize protégé sur un objet (syntaxe du destructeur en C# et C++). Le « garbage collector » appelle cette méthode quand il n'y a plus de référence valide à l'objet.

Dans certains cas, il est possible de fournir aux programmeurs utilisant un objet la possibilité de libérer explicitement ces ressources externes avant que le « garbage collector » ne libère l'objet. Si une ressource externe est rare ou coûteuse, les performances peuvent être améliorées si le programmeur libère explicitement des ressources lorsqu'elles ne sont plus utilisées. Pour fournir le contrôle explicite, implémentez le Dispose fourni par l'IDisposable. Le consommateur de l'objet doit appeler cette méthode lorsqu'il a terminé d'utiliser l'objet. Il est possible d'appeler Dispose même si d'autres références à l'objet sont actives.

Notez que vous devez fournir un nettoyage implicite à l'aide de la méthode Finalize, même quand vous fournissez un contrôle explicite avec Dispose. Finalize fournit une sauvegarde permettant d'éviter une fuite définitive des ressources si le programmeur n'appelle pas Dispose.

Pour plus d'informations sur l'implémentation de Finalize et de Dispose pour nettoyer des ressources non managées, consultez Garbage Collection. L'exemple suivant illustre le modèle de design de base pour implémenter dispose. Cet exemple requiert l'espace de noms System.

' Design pattern for a base class.

Public Class Base
   Implements IDisposable
    ' Field to handle multiple calls to Dispose gracefully.
    Dim disposed as Boolean = false

   ' Implement IDisposable.
   Public Overloads Sub Dispose() Implements IDisposable.Dispose
      Dispose(True)
      GC.SuppressFinalize(Me)
   End Sub

   Protected Overloads Overridable Sub Dispose(disposing As Boolean)
      If disposed = False Then
          If disposing Then
             ' Free other state (managed objects).
             disposed = True
          End If
          ' Free your own state (unmanaged objects).
          ' Set large fields to null.
      End If
   End Sub

   Protected Overrides Sub Finalize()
      ' Simply call Dispose(False).
      Dispose (False)
   End Sub
End Class

' Design pattern for a derived class.
Public Class Derived
   Inherits Base

    ' Field to handle multiple calls to Dispose gracefully.
    Dim disposed as Boolean = false

   Protected Overloads Overrides Sub Dispose(disposing As Boolean)
      If disposed = False Then
          If disposing Then
             ' Release managed resources.
          End If
          ' Release unmanaged resources.
          ' Set large fields to null.
          disposed = True
      End If
      ' Call Dispose on your base class.
      Mybase.Dispose(disposing)
   End Sub
   ' The derived class does not have a Finalize method
   ' or a Dispose method without parameters because it inherits
   ' them from the base class.
End Class
// Design pattern for a base class.
public class Base: IDisposable
{
    private bool disposed = false;

    //Implement IDisposable.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Free other state (managed objects).
            }
            // Free your own state (unmanaged objects).
            // Set large fields to null.
            disposed = true;
        }
    }

    // Use C# destructor syntax for finalization code.
    ~Base()
    {
        // Simply call Dispose(false).
        Dispose (false);
    }
}

// Design pattern for a derived class.
public class Derived: Base
{
    private bool disposed = false;

    protected override void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Release managed resources.
            }
            // Release unmanaged resources.
            // Set large fields to null.
           // Call Dispose on your base class.
            disposed = true;
        }
        base.Dispose(disposing);
    }
    // The derived class does not have a Finalize method
    // or a Dispose method without parameters because it inherits
    // them from the base class.
}

Le code suivant se développe l'exemple précédent pour indiquer les différentes manières Supprime est appelé et lorsque Finalize est appelée. Les étapes du modèle sont suivies disposant avec les résultats dans la console. L'allocation et la version finale d'une ressource managée est gérée dans la classe dérivée.

Imports System
Imports System.Collections.Generic
Imports System.Runtime.InteropServices

' Design pattern for a base class.
Public MustInherit Class Base
    Implements IDisposable

    Private disposed as Boolean = false
    Private instName As String
    Private trackingList As List(Of Object)

    Public Sub New(instanceName As String, tracking As List(Of Object))
        MyClass.instName = instanceName
        trackingList = tracking
        trackingList.Add(Me)
    End Sub

    Public ReadOnly Property InstanceName() As String
        Get
            Return instName
        End Get
    End Property

    'Implement IDisposable.
    Public Overloads Sub Dispose() Implements IDisposable.Dispose
        Console.WriteLine(vbNewLine + "[{0}].Base.Dispose()", instName)
        Dispose(true)
        GC.SuppressFinalize(Me)
    End Sub

    Protected Overloads Overridable Sub Dispose(disposing As Boolean)
        If disposed = False Then
            If disposing Then
                ' Free other state (managed objects).
                Console.WriteLine("[{0}].Base.Dispose(true)", instName)
                trackingList.Remove(Me)
                Console.WriteLine("[{0}] Removed from tracking list: {1:x16}",
                    instanceName, MyClass.GetHashCode())
            Else
                Console.WriteLine("[{0}].Base.Dispose(false)", instName)
            End If
            disposed = True
        End If
    End Sub

    Protected Overrides Sub Finalize()
        ' Simply call Dispose(False).
        Console.WriteLine(vbNewLine + "[{0}].Base.Finalize()", instName)
        Dispose(False)
    End Sub
End Class

' Design pattern for a derived class.
Public Class Derived
   Inherits Base

    Private disposed as Boolean = false
    Private umResource As IntPtr

    Public Sub New(instanceName As String, tracking As List(Of Object))
        MyBase.New(instanceName, tracking)
        ' Save the instance name as an unmanaged resource
        umResource = Marshal.StringToCoTaskMemAuto(instanceName)
    End Sub

    Protected Overloads Overrides Sub Dispose(disposing As Boolean)
        If disposed = False Then
            If disposing Then
                Console.WriteLine("[{0}].Derived.Dispose(true)", InstanceName)
                ' Release managed resources.
            Else
                Console.WriteLine("[{0}].Derived.Dispose(false)", InstanceName)
            End If
           ' Release unmanaged resources.
            If umResource <> IntPtr.Zero
                Marshal.FreeCoTaskMem(umResource)
                Console.WriteLine("[{0}] Unmanaged memory freed at {1:x16}", _
                    InstanceName, umResource.ToInt64())
                umResource = IntPtr.Zero
            End If
            disposed = True
        End If
        ' Call Dispose in the base class.
        MyBase.Dispose(disposing)
    End Sub
    ' The derived class does not have a Finalize method
    ' or a Dispose method without parameters because it inherits
    ' them from the base class.
End Class

Public Class TestDisposal
    Public Shared Sub Main()
        Dim tracking As New List(Of Object)()

        ' Dispose is not called, Finalize will be called later.
        Using Nothing
            Console.WriteLine(vbNewLine + "Disposal Scenario: #1" + vbNewLine)
            Dim d3 As New Derived("d1", tracking)
        End Using

        ' Dispose is implicitly called in the scope of the using statement.
        Using d1 As New Derived("d2", tracking)
            Console.WriteLine(vbNewLine + "Disposal Scenario: #2" + vbNewLine)
        End Using

        ' Dispose is explicitly called.
        Using Nothing
            Console.WriteLine(vbNewLine + "Disposal Scenario: #3" + vbNewLine)
            Dim d2 As New Derived("d3", tracking)
            d2.Dispose()
        End Using

        ' Again, Dispose is not called, Finalize will be called later.
        Using Nothing
            Console.WriteLine(vbNewLine + "Disposal Scenario: #4" + vbNewLine)
            Dim d4 As New Derived("d4", tracking)
        End Using

        ' List the objects remaining to dispose.
        Console.WriteLine(vbNewLine + "Objects remaining to dispose = {0:d}", tracking.Count)
        For Each dd As Derived in tracking
            Console.WriteLine("    Reference Object: {0:s}, {1:x16}",
                dd.InstanceName, dd.GetHashCode())
        Next dd
        ' Queued finalizers will be exeucted when Main() goes out of scope.
        Console.WriteLine(vbNewLine + "Dequeueing finalizers...")
    End Sub
End Class

' The program will display output similar to the following:
'
' Disposal Scenario: #1
'
'
' Disposal Scenario: #2
'
'
' [d2].Base.Dispose()
' [d2].Derived.Dispose(true)
' [d2] Unmanaged memory freed at 00000000001ce420
' [d2].Base.Dispose(true)
' [d2] Removed from tracking list: 0000000002bf8098
'
' Disposal Scenario: #3
'
'
' [d3].Base.Dispose()
' [d3].Derived.Dispose(true)
' [d3] Unmanaged memory freed at 00000000001ce420
' [d3].Base.Dispose(true)
' [d3] Removed from tracking list: 0000000000bb8560
'
' Disposal Scenario: #4
'
'
' Objects remaining to dispose = 2
'     Reference Object: d1, 000000000297b065
'     Reference Object: d4, 0000000003553390
'
' Dequeueing finalizers...
'
' [d4].Base.Finalize()
' [d4].Derived.Dispose(false)
' [d4] Unmanaged memory freed at 00000000001ce420
' [d4].Base.Dispose(false)
'
' [d1].Base.Finalize()
' [d1].Derived.Dispose(false)
' [d1] Unmanaged memory freed at 00000000001ce3f0
' [d1].Base.Dispose(false)
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

// Design pattern for a base class.
public abstract class Base : IDisposable
{
    private bool disposed = false;
    private string instanceName;
    private List<object> trackingList;

    public Base(string instanceName, List<object> tracking)
    {
        this.instanceName = instanceName;
         trackingList = tracking;
         trackingList.Add(this);
    }

    public string InstanceName
    {
        get
        {
            return instanceName;
        }
    }

    //Implement IDisposable.
    public void Dispose()
    {
        Console.WriteLine("\n[{0}].Base.Dispose()", instanceName);
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Free other state (managed objects).
                Console.WriteLine("[{0}].Base.Dispose(true)", instanceName);
                trackingList.Remove(this);
                Console.WriteLine("[{0}] Removed from tracking list: {1:x16}",
                    instanceName, this.GetHashCode());
            }
            else
            {
                Console.WriteLine("[{0}].Base.Dispose(false)", instanceName);
            }
            disposed = true;
        }
    }

    // Use C# destructor syntax for finalization code.
    ~Base()
    {
        // Simply call Dispose(false).
        Console.WriteLine("\n[{0}].Base.Finalize()", instanceName);
        Dispose(false);
    }
}

// Design pattern for a derived class.
public class Derived : Base
{
    private bool disposed = false;
    private IntPtr umResource;

    public Derived(string instanceName, List<object> tracking) :
        base(instanceName, tracking)
    {
         // Save the instance name as an unmanaged resource
         umResource = Marshal.StringToCoTaskMemAuto(instanceName);
    }

    protected override void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                Console.WriteLine("[{0}].Derived.Dispose(true)", InstanceName);
                // Release managed resources.
            }
            else
            {
                Console.WriteLine("[{0}].Derived.Dispose(false)", InstanceName);
            }
            // Release unmanaged resources.
            if (umResource != IntPtr.Zero)
            {
                Marshal.FreeCoTaskMem(umResource);
                Console.WriteLine("[{0}] Unmanaged memory freed at {1:x16}",
                    InstanceName, umResource.ToInt64());
                umResource = IntPtr.Zero;
            }
            disposed = true;
        }
        // Call Dispose in the base class.
        base.Dispose(disposing);
    }
    // The derived class does not have a Finalize method
    // or a Dispose method without parameters because it inherits
    // them from the base class.
}

public class TestDisposal
{
    public static void Main()
    {
        List<object> tracking = new List<object>();

        // Dispose is not called, Finalize will be called later.
        using (null)
        {
            Console.WriteLine("\nDisposal Scenario: #1\n");
            Derived d3 = new Derived("d1", tracking);
        }

        // Dispose is implicitly called in the scope of the using statement.
        using (Derived d1 = new Derived("d2", tracking))
        {
            Console.WriteLine("\nDisposal Scenario: #2\n");
        }

        // Dispose is explicitly called.
        using (null)
        {
            Console.WriteLine("\nDisposal Scenario: #3\n");
            Derived d2 = new Derived("d3", tracking);
            d2.Dispose();
        }

        // Again, Dispose is not called, Finalize will be called later.
        using (null)
        {
            Console.WriteLine("\nDisposal Scenario: #4\n");
            Derived d4 = new Derived("d4", tracking);
        }

        // List the objects remaining to dispose.
        Console.WriteLine("\nObjects remaining to dispose = {0:d}", tracking.Count);
        foreach (Derived dd in tracking)
        {
            Console.WriteLine("    Reference Object: {0:s}, {1:x16}",
                dd.InstanceName, dd.GetHashCode());
        }

        // Queued finalizers will be exeucted when Main() goes out of scope.
        Console.WriteLine("\nDequeueing finalizers...");
    }
}

// The program will display output similar to the following:
//
// Disposal Scenario: #1
//
//
// Disposal Scenario: #2
//
//
// [d2].Base.Dispose()
// [d2].Derived.Dispose(true)
// [d2] Unmanaged memory freed at 000000000034e420
// [d2].Base.Dispose(true)
// [d2] Removed from tracking list: 0000000002bf8098
//
// Disposal Scenario: #3
//
//
// [d3].Base.Dispose()
// [d3].Derived.Dispose(true)
// [d3] Unmanaged memory freed at 000000000034e420
// [d3].Base.Dispose(true)
// [d3] Removed from tracking list: 0000000000bb8560
//
// Disposal Scenario: #4
//
//
// Objects remaining to dispose = 2
//    Reference Object: d1, 000000000297b065
//    Reference Object: d4, 0000000003553390
//
// Dequeueing finalizers...
//
// [d4].Base.Finalize()
// [d4].Derived.Dispose(false)
// [d4] Unmanaged memory freed at 000000000034e420
// [d4].Base.Dispose(false)
//
// [d1].Base.Finalize()
// [d1].Derived.Dispose(false)
// [d1] Unmanaged memory freed at 000000000034e3f0
// [d1].Base.Dispose(false)

Pour obtenir un exemple de code supplémentaires montrant le modèle de design pour implémenter finalisez et dispose, consultezimplémenter une méthode Dispose.

Personnalisation d'un nom de méthode Dispose

Un nom spécifique à un domaine est parfois plus approprié que Dispose. Par exemple, il se peut qu'une encapsulation de fichier veuille utiliser le nom de méthode Close. Dans ce cas, implémentez Dispose de manière privée et créez une méthode Close publique qui appelle Dispose. L'exemple de code suivant illustre ce modèle. Vous pouvez remplacer Close par un nom de méthode plus approprié à votre domaine. Cet exemple requiert l'espace de noms System.

' Do not make this method overridable.
' A derived class should not be allowed
' to override this method.
Public Sub Close()
   ' Call the Dispose method with no parameters.
   Dispose()
End Sub
// Do not make this method virtual.
// A derived class should not be allowed
// to override this method.
public void Close()
{
   // Call the Dispose method with no parameters.
   Dispose();
}

Finalize

Les règles suivantes mettent en avant les indications d'utilisation pour la méthode Finalize :

  • Implémentez uniquement Finalize sur des objets qui nécessitent une finalisation. Des coûts de performance sont liés aux méthodes Finalize.

  • Si vous avez besoin d'une méthode Finalize, vous pouvez envisager d'implémenter IDisposable pour permettre à des utilisateurs de votre classe d'éviter le coût de l'appel à la méthode Finalize.

  • Ne rendez pas la méthode Finalize plus visible. Elle doit être protected, et non public.

  • La méthode Finalize d'un objet doit libérer toute ressource externe dont il dispose. De plus, une méthode Finalize ne doit libérer que les ressources conservées par l'objet. La méthode Finalize ne doit référencer aucun autre objet.

  • N'appelez pas directement une méthode Finalize sur un objet autre que la classe de base de l'objet. Cette opération n'est pas valide dans le langage de programmation C#.

  • Appelez la méthode Finalize de la classe de base à partir de la méthode Finalize d'un objet.

    RemarqueRemarque

    La méthode Finalize de la classe de base est appelée automatiquement avec la syntaxe du destructeur C# et C++.

Dispose

Les règles suivantes décrivent les instructions d'utilisation de la méthode Dispose :

  • Implémentez le modèle de design Dispose sur un type encapsulant des ressources qui doivent être explicitement libérées. Les utilisateurs peuvent libérer des ressources externes en appelant la méthode Dispose publique.

  • Implémentez le modèle de design Dispose sur un type de base qui comporte généralement des types dérivés rattachés à des ressources, même si le type de base n'y est pas rattaché. Si le type de base dispose d'une méthode Close, cela signifie souvent qu'il faut implémenter Dispose. Dans ces cas-là, n'implémentez pas de méthode Finalize sur le type de base. Finalize doit être implémentée dans tout type dérivé qui introduit des ressources nécessitant un nettoyage.

  • Libérez toute ressource pouvant être supprimée et dont un type est propriétaire dans sa méthode Dispose.

  • Une fois que le Dispose a été appelé sur une instance, empêchez la méthode Finalize de s'exécuter en appelant le GC.SuppressFinalize. L'exécution de tâches dans Finalize qui ne sont pas couvertes par Dispose constitue l'exception à cette règle mais cette situation se présente rarement.

  • Appelez la méthode Dispose de la classe de base si elle implémente IDisposable.

  • Ne supposez pas que la méthode Dispose sera appelée. Les ressources non managées détenues par un type doivent aussi être libérées dans une méthode Finalize au cas où la méthode Dispose n'est pas appelée.

  • Levez une exception ObjectDisposedException à partir des méthodes d'instance sur ce type (autres que Dispose) quand des ressources sont déjà supprimées. Cette règle ne s'applique pas à la méthode Dispose car elle doit pouvoir être appelée plusieurs fois sans lever d'exception.

  • Propagez les appels à Dispose par le biais de la hiérarchie de types de base. La méthode Dispose doit aussi libérer toutes les ressources détenues par cet objet et tout objet détenu par cet objet. Par exemple, vous pouvez créer un objet tel que TextReader qui détient Stream et Encoding, lesquels sont créés par TextReader sans que l'utilisateur ne le sache. De plus, Stream et Encoding peuvent tous deux obtenir des ressources externes. Quand vous appelez la méthode Dispose sur TextReader, celui-ci doit à son tour appeler Dispose sur Stream et sur Encoding, provoquant la libération de leurs ressources externes.

  • Vous devez envisager de rendre un objet inutilisable après l'appel à sa méthode Dispose. Il est difficile d'implémenter le modèle consistant à recréer un objet déjà supprimé.

  • Autorisez plusieurs appels à une méthode Dispose sans levée d'exception. La méthode ne doit pas réagir après le premier appel.

Portions Copyright 2005 Microsoft Corporation. Tous droits réservés.

Portions Copyright Addison-Wesley Corporation. Tous droits réservés.

Pour plus d'informations sur les règles de conception, consultez « règles de conception d'infrastructure : Conventions idiomes et modèles carnet de bibliothèques réutilisables framework » Krzysztof Cwalina et Brad Abrams, publiés par Addison-Wesley, 2005.

Voir aussi

Référence

IDisposable.Dispose

Object.Finalize

Concepts

Garbage Collection

Autres ressources

Instructions de conception pour le développement de bibliothèques de classes

Modèles de design