Implémenter une méthode Dispose
La méthode Dispose est principalement implémentée pour libérer des ressources non managées. Lorsque vous travaillez avec des membres d’instance qui sont des implémentations IDisposable, il est courant d’effectuer des appels Dispose en cascade. Il existe d’autres raisons d’implémenter Dispose, par exemple, pour libérer de la mémoire allouée, supprimer un élément ajouté à une collection ou signaler la libération d’un verrou acquis.
Le récupérateur de mémoire .NET n’alloue pas de mémoire non managée, et n’en libère pas non plus. Le modèle pour supprimer un objet, dénommé modèle Dispose, impose un ordre sur la durée de vie d’un objet. Le pattern dispose est utilisé pour les objets qui implémentent l’interface IDisposable. Ce modèle est courant lors de l’interaction avec des handles de fichiers et de canaux, des handles de Registre, des handles d’attente ou des pointeurs vers des blocs de mémoire non managée, car le récupérateur de mémoire ne peut pas récupérer des objets non managés.
Afin de garantir que les ressources sont toujours nettoyées de manière appropriée, une méthode Dispose doit être idempotente, afin qu'elle puisse être appelée plusieurs fois sans lever d'exception. En outre, les appels ultérieurs de Dispose ne doivent rien faire.
L’exemple de code fourni pour la méthode GC.KeepAlive montre comment le nettoyage de la mémoire peut entraîner l’exécution d’un finaliseur pendant 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 la collecte de déchets 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 de services dans un IServiceCollection, la durée de vie du service est gérée implicitement en votre nom. Les IServiceProvider et les IHost correspondants coordonnent le nettoyage des ressources. Plus précisément, les implémentations de IDisposable et de 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.
Poignées sûres
L’écriture de code pour le finaliseur d’un objet est une tâche complexe qui peut entraîner des problèmes s’il n’est pas effectué correctement. Par conséquent, nous vous recommandons de construire System.Runtime.InteropServices.SafeHandle objets 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 et sur Unix, un descripteur de fichier. Le SafeHandle
fournit toute la logique nécessaire pour s’assurer que cette ressource est libérée une seule fois, soit lorsque l'SafeHandle
est supprimé, soit lorsque toutes les références à l'SafeHandle
ont été supprimées et que l’instance de SafeHandle
est finalisée.
La System.Runtime.InteropServices.SafeHandle est une classe de base abstraite. Les classes dérivées fournissent des instances spécifiques pour différents types de poignées. Ces classes dérivées vérifient quelles valeurs de l'System.IntPtr sont considérées comme invalides et comment libérer réellement la poignée. Par exemple, SafeFileHandle dérive de SafeHandle
pour encapsuler IntPtrs
qui identifie les handles/descripteurs de fichier ouverts, et substitue sa méthode SafeHandle.ReleaseHandle() pour les fermer (via la fonction close
sur Unix ou la fonction CloseHandle
sur Windows). La plupart des API des bibliothèques .NET qui créent une ressource non managée l’encapsulent dans un SafeHandle
et renvoient ce SafeHandle
en fonction des besoins, plutôt que de renvoyer le pointeur brut. Dans les situations où vous interagissez avec un composant non managé et obtenez une IntPtr
pour une ressource non managée, vous pouvez créer votre propre type de SafeHandle
pour l’encapsuler. Par conséquent, peu de types non-SafeHandle
ont besoin d'implémenter des finaliseurs. La plupart des implémentations de modèle Dispose finissent uniquement par encapsuler d’autres ressources managées, dont certaines peuvent être des objets SafeHandle
.
Les classes dérivées suivantes dans l’espace de noms Microsoft.Win32.SafeHandles fournissent des handles sécurisés.
Classe | Ressources qu’il contient |
---|---|
SafeFileHandle SafeMemoryMappedFileHandle SafePipeHandle |
Fichiers, fichiers mappés en mémoire et canaux |
SafeMemoryMappedViewHandle | Vues de la mémoire |
SafeNCryptKeyHandle SafeNCryptProviderHandle SafeNCryptSecretHandle |
Constructions de chiffrement |
SafeRegistryHandle | Clés de Registre |
SafeWaitHandle | Handles d'attente |
Dispose() et Dispose(bool)
L’interface IDisposable nécessite l’implémentation d’une méthode sans paramètre unique, Dispose. En outre, toute classe non scellée doit avoir une méthode de surcharge Dispose(bool)
.
Les signatures de méthode sont les suivantes :
public
non virtuel (NotOverridable
en Visual Basic) ( implémentationIDisposable.Dispose).protected virtual
(Overridable
en Visual Basic)Dispose(bool)
.
Méthode Dispose()
Étant donné que la méthode public
, non virtuelle (NotOverridable
en Visual Basic), la méthode Dispose
sans paramètre 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, le cas échéant, n’a pas à s’exécuter. La libération de la mémoire réelle associée à un objet managé est toujours du domaine du récupérateur de mémoire. En raison de cela, il a une implémentation standard :
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. Le nettoyage réel est effectué par la surcharge de méthode Dispose(bool)
.
Surcharge de méthode Dispose(bool)
Dans la surcharge, le paramètre disposing
est un Boolean qui indique si l’appel de méthode provient d’une méthode Dispose (sa valeur est true
) ou d’un finaliseur (sa valeur est false
).
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// Dispose managed state (managed objects).
// ...
}
// Free unmanaged resources.
// ...
_disposed = true;
}
Protected Overridable Sub Dispose(disposing As Boolean)
If disposed Then Exit Sub
If disposing Then
' Free managed resources.
' ...
End If
' Free unmanaged resources.
' ...
disposed = True
End Sub
Important
Le paramètre disposing
doit être false
lorsqu’il est appelé à partir d’un finaliseur et true
lorsqu’il est appelé à partir de la méthode IDisposable.Dispose. En d’autres termes, il est true
lorsqu’il est appelé de façon déterministe et false
lorsqu’il n’est pas déterministement appelé.
Le corps de la méthode se compose de trois blocs de code :
Bloc de retour conditionnel si l’objet est déjà supprimé.
Bloc conditionnel qui libère des ressources managées. Ce bloc s’exécute si la valeur de
disposing
esttrue
. 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 implémentation de Dispose (suppression 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’implémentation SafeHandle.Dispose() ici.
- Objets managés qui consomment de grandes quantités de mémoire ou consomment des ressources rares. Assignez les références des objets gérés volumineux à
null
pour les rendre moins accessibles. Cela les libère plus rapidement qu’en cas de récupération non déterministe.
Bloc qui libère des ressources non managées. Ce bloc s’exécute indépendamment de la valeur du paramètre
disposing
.
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 implémentation de IDisposable 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 par le biais de la méthode Dispose. Dans l’exemple suivant, la classe est sealed
(ou NotInheritable
en 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
- Si votre classe a un champ ou une propriété IDisposable mais n'pas sa propre, ce qui signifie que la classe ne crée pas l’objet, la classe n’a pas besoin d’implémenter IDisposable.
- Il existe des cas où vous souhaiterez peut-être effectuer la vérification de
null
dans un finaliseur (ce qui inclut la méthodeDispose(false)
appelée par un finaliseur). L’une des principales raisons 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 Dispose
Toutes les classes non scellées (ou les classes Visual Basic 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, vous devez fournir les éléments suivants :
- Implémentation Dispose qui appelle la méthode
Dispose(bool)
. - Méthode
Dispose(bool)
qui effectue le nettoyage réel. - Une classe dérivée de SafeHandle qui encapsule votre ressource non managée (recommandée) ou une substitution à la méthode Object.Finalize. La classe SafeHandle fournit un finaliseur, de sorte que vous n’avez pas à écrire vous-même.
Important
Il est possible qu’une classe de base référence uniquement des objets managés et implémente le modèle de suppression. Dans ces cas, un finaliseur n’est pas nécessaire. Un finaliseur n’est nécessaire que si vous référencez directement des ressources non managées.
Voici un exemple général d’implémentation du modèle de suppression pour une classe de base qui utilise un handle sécurisé.
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
public 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);
GC.SuppressFinalize(this);
}
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_safeHandle?.Dispose();
_safeHandle = null;
}
_disposedValue = true;
}
}
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices
Public Class BaseClassWithSafeHandle
Implements IDisposable
' To detect redundant calls
Private _disposedValue As Boolean
' Instantiate a SafeHandle instance.
Private _safeHandle 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(ByVal disposing As Boolean)
If Not _disposedValue Then
If disposing Then
_safeHandle?.Dispose()
_safeHandle = Nothing
End If
_disposedValue = True
End If
End Sub
End Class
Remarque
L’exemple précédent utilise un objet SafeFileHandle pour illustrer le modèle ; tout objet dérivé de SafeHandle peut être utilisé à la place. 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 pour une classe de base qui remplace Object.Finalize.
using System;
public 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;
}
}
}
Public Class BaseClassWithFinalizer
Implements IDisposable
' To detect redundant calls
Private _disposedValue As Boolean
Protected Overrides Sub Finalize()
Dispose(False)
End Sub
' 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(ByVal disposing As Boolean)
If Not _disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects)
End If
' TODO free unmanaged resources (unmanaged objects) And override finalizer
' TODO: set large fields to null
_disposedValue = True
End If
End Sub
End Class
Conseil
En C#, vous implémentez une finalisation en fournissant un finaliseur, et non en substituant Object.Finalize. Dans Visual Basic, vous créez un finaliseur avec Protected Overrides Sub Finalize()
.
Implémenter le modèle de suppression pour 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 classe de base de IDisposable.Dispose est héritée par ses classes dérivées. Au lieu de cela, pour nettoyer une classe dérivée, vous fournissez les éléments suivants :
- Méthode
protected override void Dispose(bool)
qui remplace la méthode de classe de base et effectue le nettoyage réel de la classe dérivée. Cette méthode doit également appeler la méthodebase.Dispose(bool)
(MyBase.Dispose(bool)
en Visual Basic) en lui transmettant l’état de suppression (paramètrebool disposing
) en tant qu’argument. - Une classe dérivée de SafeHandle qui encapsule votre ressource non managée (recommandée) ou une substitution à la méthode Object.Finalize. La classe SafeHandle fournit un finaliseur qui vous libère d’avoir à coder un. Si vous fournissez un finaliseur, il doit appeler la surcharge
Dispose(bool)
avec un argumentfalse
.
Voici un exemple du modèle général d’implémentation du modèle Dispose pour une classe dérivée qui utilise un handle sécurisé :
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
public 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();
_safeHandle = null;
}
_disposedValue = true;
}
// Call base class implementation.
base.Dispose(disposing);
}
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices
Public Class DerivedClassWithSafeHandle
Inherits BaseClassWithSafeHandle
' To detect redundant calls
Private _disposedValue As Boolean
' Instantiate a SafeHandle instance.
Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If Not _disposedValue Then
If disposing Then
_safeHandle?.Dispose()
_safeHandle = Nothing
End If
_disposedValue = True
End If
' Call base class implementation.
MyBase.Dispose(disposing)
End Sub
End Class
Remarque
L’exemple précédent utilise un objet SafeFileHandle pour illustrer le modèle ; tout objet dérivé de SafeHandle peut être utilisé à la place. 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 pour une classe dérivée qui remplace Object.Finalize:
public class DerivedClassWithFinalizer : BaseClassWithFinalizer
{
// To detect redundant calls
private bool _disposedValue;
~DerivedClassWithFinalizer() => 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);
}
}
Public Class DerivedClassWithFinalizer
Inherits BaseClassWithFinalizer
' To detect redundant calls
Private _disposedValue As Boolean
Protected Overrides Sub Finalize()
Dispose(False)
End Sub
' Protected implementation of Dispose pattern.
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If Not _disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects).
End If
' TODO free unmanaged resources (unmanaged objects) And override a finalizer below.
' TODO: set large fields to null.
_disposedValue = True
End If
' Call the base class implementation.
MyBase.Dispose(disposing)
End Sub
End Class