Implémenter une méthode Dispose

L’implémentation de la Dispose méthode est principalement destinée à libérer des ressources non managées. Lors de l’utilisation de membres d’instance qui sont des IDisposable implémentations, il est courant d’appeler en cascade Dispose . Il existe des raisons supplémentaires d’implémenter Dispose , par exemple, pour libérer de la mémoire qui a été allouée, supprimer un élément qui a été ajouté à une collection ou signaler la libération d’un verrou acquis.

Le garbage collector .net n’alloue pas ou ne libère pas de mémoire non managée. Le modèle de suppression d’un objet, appelé modèle de suppression, impose un ordre sur la durée de vie d’un objet. Le modèle de suppression est utilisé pour les objets qui implémentent l’interface et est courant lors de l' IDisposable interaction avec les handles de fichiers et de canaux, les handles de Registre, les handles d’attente ou les pointeurs vers des blocs de mémoire non managée. Cela est dû au fait que le garbage collector ne peut pas récupérer les objets non managés.

Pour garantir que les ressources sont toujours nettoyées de manière appropriée, une Dispose méthode doit être idempotent, de sorte qu’elle peut être appelée plusieurs fois sans lever d’exception. En outre, les appels suivants de Dispose ne doivent rien faire.

L’exemple de code fourni pour la GC.KeepAlive méthode montre comment garbage collection peut provoquer l’exécution d’un finaliseur, alors qu’une référence non managée à l’objet ou à ses membres est toujours en cours d’utilisation. Il peut être judicieux d’utiliser GC.KeepAlive pour rendre l’objet inéligible pour garbage collection du début de la routine actuelle jusqu’au point où cette méthode est appelée.

Conseil

En ce qui concerne l’injection de dépendances, lors de l’inscription des services dans un IServiceCollection , la IServiceCollection est gérée implicitement en votre nom. Et orchestrent le IServiceProvider nettoyage des ressources correspondant IHost . Plus précisément, les implémentations de IDisposable et IAsyncDisposable sont correctement supprimées à la fin de leur durée de vie spécifiée.

Pour plus d’informations, consultez injection de dépendances dans .net.

handles de Coffre

L’écriture de code pour le finaliseur d’un objet est une tâche complexe qui peut provoquer des problèmes si elle n’est pas effectuée correctement. Par conséquent, nous vous recommandons de construire des objets System.Runtime.InteropServices.SafeHandle au lieu d'implémenter un finaliseur.

Un System.Runtime.InteropServices.SafeHandle est un type managé abstrait qui encapsule un System.IntPtr qui identifie une ressource non managée. Sur Windows, il peut identifier un handle sur UNIX, un descripteur de fichier. Il fournit toute la logique nécessaire pour s’assurer que cette ressource est libérée une seule fois, lorsque la SafeHandle est supprimée de ou lorsque toutes les références à SafeHandle ont été supprimées et que l' SafeHandle instance est finalisée.

Est une classe de System.Runtime.InteropServices.SafeHandle base abstraite. Les classes dérivées fournissent des instances spécifiques pour différents genres de handles. Ces classes dérivées valident les valeurs de System.IntPtr qui sont considérées comme non valides et comment libérer le handle. Par exemple, SafeFileHandle dérive SafeHandle de à Wrap IntPtrs qui identifient les descripteurs/descripteurs de fichiers ouverts et substitue sa SafeHandle.ReleaseHandle() méthode pour le fermer (via la close fonction sur UNIX ou CloseHandle Function sur Windows). La plupart des API dans les bibliothèques .NET qui créent une ressource non managée l’encapsulent dans un SafeHandle et le renvoient SafeHandle en fonction des besoins, plutôt que de transmettre le pointeur brut. Dans les situations où vous interagissez avec un composant non managé et récupérez un IntPtr pour une ressource non managée, vous pouvez créer votre propre SafeHandle type pour l’encapsuler. Par conséquent, peu de types n’ont pas SafeHandle besoin d’implémenter des finaliseurs ; la plupart des implémentations de modèle jetables finissent par encapsuler d’autres ressources managées, dont certaines peuvent être SafeHandle des s.

Les classes dérivées suivantes de l'espace de noms Microsoft.Win32.SafeHandles fournissent des handles sécurisés :

Dispose () et dispose (bool)

L'interface IDisposable requiert l'implémentation d'une méthode unique sans paramètre, Dispose. En outre, toute classe non scellée doit avoir une méthode de surcharge supplémentaire Dispose(bool) .

Les signatures de méthode sont :

  • publicnon virtuel ( NotOverridable en Visual Basic) ( IDisposable.Dispose implémentation).
  • protected virtual( Overridable en Visual Basic) Dispose(bool) .

La méthode Dispose ()

étant donné que le, non virtuel ( NotOverridable en Visual Basic), la public méthode sans paramètre Dispose est appelée lorsqu’elle n’est plus nécessaire (par un consommateur du type), son objectif est de libérer des ressources non managées, d’effectuer un nettoyage général et d’indiquer que le finaliseur, s’il en existe un, ne doit pas s’exécuter. La libération de la mémoire réelle associée à un objet géré est toujours le domaine du garbage collector. De ce fait, son implémentation standard est la suivante :

public void Dispose()
{
    // Dispose of unmanaged resources.
    Dispose(true);
    // Suppress finalization.
    GC.SuppressFinalize(this);
}
Public Sub Dispose() _
    Implements IDisposable.Dispose
    ' Dispose of unmanaged resources.
    Dispose(True)
    ' Suppress finalization.
    GC.SuppressFinalize(Me)
End Sub

La méthode Dispose effectue le nettoyage de tous les objets, le récupérateur de mémoire n'a plus donc besoin d'appeler la remplacement de Object.Finalize des objets. Par conséquent, l'appel à la méthode SuppressFinalize empêche le récupérateur de mémoire d'exécuter le finaliseur. Si le type n'a pas de finaliseur, l'appel à GC.SuppressFinalize n'a aucun effet. Notez que le nettoyage réel est effectué par la Dispose(bool) surcharge de méthode.

Surcharge de la méthode Dispose (bool)

Dans la surcharge, le disposing paramètre est un Boolean qui indique si l’appel de méthode provient d’une Dispose méthode (sa valeur est true ) ou d’un finaliseur (sa valeur est false ).

protected virtual void Dispose(bool disposing)
{
    if (_disposed)
    {
        return;
    }

    if (disposing)
    {
        // TODO: dispose managed state (managed objects).
    }

    // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
    // TODO: set large fields to null.

    _disposed = true;
}
Protected Overridable Sub Dispose(disposing As Boolean)
     If disposed Then Exit Sub	

     ' A block that frees unmanaged resources.
     
     If disposing Then
         ' Deterministic call…
         ' A conditional block that frees managed resources.    	
     End If
     
     disposed = True
End Sub

Important

Le disposing paramètre doit être lorsqu’il est appelé à partir d’un finaliseur, et true lorsqu’il est false appelé à partir de la IDisposable.Dispose méthode. En d’autres termes, il est quand il est appelé de façon déterministe et false lorsqu’il n’est true pas appelé de manière déterministe.

Le corps de la méthode se compose de trois blocs de code :

  • Bloc pour un retour conditionnel si l’objet est déjà supprimé.

  • Un bloc qui libère les ressources non managées. Ce bloc s'exécute indépendamment de la valeur du paramètre disposing.

  • Un bloc conditionnel qui libère les ressources managées. Ce bloc s'exécute si la valeur de disposing est true. Les ressources managées qu'il libère peuvent inclure :

    • Objets managés qui implémentent IDisposable . Le bloc conditionnel peut être utilisé pour appeler leur Dispose implémentation (dispose en cascade). Si vous avez utilisé une classe dérivée de System.Runtime.InteropServices.SafeHandle pour encapsuler votre ressource non managée, vous devez appeler l' SafeHandle.Dispose() implémentation ici.

    • Objets managés qui consomment de grandes quantités de mémoire ou consomment des ressources rares. Affectez des références d’objets managés volumineux à null pour les rendre plus susceptibles d’être inaccessibles. Cela les libère plus rapidement que s’ils étaient récupérés de façon non déterministe.

Si l’appel de méthode provient d’un finaliseur, seul le code qui libère les ressources non managées doit s’exécuter. L’implémenteur est chargé de s’assurer que le chemin d’accès faux n’interagit pas avec les objets managés qui ont peut-être été supprimés. Cela est important, car l’ordre dans lequel le garbage collector supprime les objets managés pendant la finalisation n’est pas déterministe.

Appels de suppression en cascade

Si votre classe possède un champ ou une propriété et que son type implémente IDisposable , la classe conteneur elle-même doit également implémenter IDisposable . Une classe qui instancie une IDisposable implémentation et la stocke en tant que membre d’instance est également responsable de son nettoyage. Cela permet de s’assurer que les types jetables référencés ont la possibilité d’effectuer un nettoyage de manière déterministe à l’aide de la Dispose méthode. Dans cet exemple, la classe est sealed (ou NotInheritable dans Visual Basic).

using System;

public sealed class Foo : IDisposable
{
    private readonly IDisposable _bar;

    public Foo()
    {
        _bar = new Bar();
    }

    public void Dispose() => _bar.Dispose();
}
Public NotInheritable Class Foo
    Implements IDisposable

    Private ReadOnly _bar As IDisposable

    Public Sub New()
        _bar = New Bar()
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        _bar.Dispose()
    End Sub
End Class

Conseil

Dans certains cas, vous pouvez être amené à effectuer null des vérifications dans un finaliseur (qui inclut la Dispose(false) méthode appelée par un finaliseur), l’une des raisons principales est si vous ne savez pas si l’instance a été entièrement initialisée (par exemple, une exception peut être levée dans un constructeur).

Implémenter le modèle de suppression

toutes les classes non-sealed (ou Visual Basic classes non modifiées comme NotInheritable ) doivent être considérées comme une classe de base potentielle, car elles peuvent être héritées. Si vous implémentez le modèle de suppression pour une classe de base potentielle quelconque, vous devez fournir les éléments suivants :

  • Une implémentation de Dispose qui appelle la méthode Dispose(bool).
  • Dispose(bool)Méthode qui effectue le nettoyage réel.
  • Une classe dérivée de SafeHandle qui encapsule votre ressource managée (recommandée) ou une substitution de la méthode Object.Finalize. La SafeHandle classe fournit un finaliseur, donc vous n’avez pas besoin d’en écrire un vous-même.

Important

Une classe de base peut uniquement référencer des objets managés et implémenter le modèle de suppression. Dans ce cas, un finaliseur n’est pas nécessaire. Un finaliseur est requis uniquement si vous référencez directement des ressources non managées.

Voici un exemple du modèle général d’implémentation du modèle de suppression d’une classe de base qui utilise un handle sécurisé.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

class BaseClassWithSafeHandle : IDisposable
{
    // To detect redundant calls
    private bool _disposedValue;

    // Instantiate a SafeHandle instance.
    private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose() => Dispose(true);

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle.Dispose();
            }

            _disposedValue = true;
        }
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class BaseClassWithSafeHandle : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    ' Public implementation of Dispose pattern callable by consumers.
    Public Sub Dispose() _
               Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overridable Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
        End If

        disposed = True
    End Sub
End Class

Notes

L'exemple précédent utilise un objet SafeFileHandle pour illustrer le modèle, mais il est possible d'utiliser à la place n'importe quel objet dérivé de SafeHandle. Notez que l'exemple n'instancie pas correctement son objet SafeFileHandle.

Voici le modèle général d’implémentation du modèle de suppression d’une classe de base qui remplace Object.Finalize.

using System;

class BaseClassWithFinalizer : IDisposable
{
    // To detect redundant calls
    private bool _disposedValue;

    ~BaseClassWithFinalizer() => Dispose(false);

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects)
            }

            // TODO: free unmanaged resources (unmanaged objects) and override finalizer
            // TODO: set large fields to null
            _disposedValue = true;
        }
    }
}
Class BaseClassWithFinalizer : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' Public implementation of Dispose pattern callable by consumers.
    Public Sub Dispose() _
               Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overridable Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True
    End Sub

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub
End Class

Conseil

En C#, vous implémentez une finalisation en fournissant un finaliseur, et non en substituant . dans Visual Basic, vous créez un finaliseur avec Protected Overrides Sub Finalize() .

Implémenter le modèle de suppression d’une classe dérivée

Une classe dérivée d'une classe qui implémente l'interface IDisposable ne doit pas implémenter IDisposable, car l'implémentation de la classe de base de IDisposable.Dispose est héritée par les classes dérivées. Au lieu de cela, pour nettoyer une classe dérivée, vous devez fournir les éléments suivants :

  • protected override void Dispose(bool)Méthode qui remplace la méthode de la classe de base et effectue le nettoyage réel de la classe dérivée. cette méthode doit également appeler la base.Dispose(bool) méthode ( MyBase.Dispose(bool) dans Visual Basic) en lui passant l’état de suppression ( bool disposing paramètre) en tant qu’argument.
  • Une classe dérivée de SafeHandle qui encapsule votre ressource managée (recommandée) ou une substitution de la méthode Object.Finalize. La classe SafeHandle fournit un finaliseur qui vous permet de ne pas avoir à en coder un. Si vous fournissez un finaliseur, il doit appeler la surcharge avec false l' Dispose(bool) argument.

Voici un exemple du modèle général d’implémentation du modèle de suppression d’une classe dérivée qui utilise un handle sécurisé :

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

class DerivedClassWithSafeHandle : BaseClassWithSafeHandle
{
    // To detect redundant calls
    private bool _disposedValue;

    // Instantiate a SafeHandle instance.
    private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle.Dispose();
            }

            _disposedValue = true;
        }

        // Call base class implementation.
        base.Dispose(disposing);
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class DerivedClassWithSafeHandle : Inherits BaseClassWithSafeHandle
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
            ' Free any other managed objects here.
            '
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

        ' Call base class implementation.
        MyBase.Dispose(disposing)
    End Sub
End Class

Notes

L'exemple précédent utilise un objet SafeFileHandle pour illustrer le modèle, mais il est possible d'utiliser à la place n'importe quel objet dérivé de SafeHandle. Notez que l'exemple n'instancie pas correctement son objet SafeFileHandle.

Voici le modèle général d'implémentation du modèle de suppression d'une classe dérivée qui remplace Object.Finalize :

class DerivedClassWithFinalizer : BaseClassWithFinalizer
{
    // To detect redundant calls
    private bool _disposedValue;

    ~DerivedClassWithFinalizer() => this.Dispose(false);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects).
            }

            // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
            // TODO: set large fields to null.
            _disposedValue = true;
        }

        // Call the base class implementation.
        base.Dispose(disposing);
    }
}
Class DerivedClassWithFinalizer : Inherits BaseClassWithFinalizer
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

        ' Call the base class implementation.
        MyBase.Dispose(disposing)
    End Sub

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub
End Class

Voir aussi