Utilisation d’objets implémentant IDisposable

Le récupérateur de mémoire (GC) du Common Language Runtime récupère la mémoire utilisée par les objets managés. En règle générale, les types qui utilisent des ressources non managées implémentent l’interface IDisposable ou IAsyncDisposable pour permettre la récupération des ressources non managées. Lorsque vous avez terminé d’utiliser un objet qui implémente IDisposable, vous appelez l’implémentation de l’objet Dispose ou DisposeAsync pour effectuer explicitement le nettoyage. Vous pouvez le faire de deux façons :

  • Avec l’instruction ou la déclaration using C# (Using en Visual Basic).
  • En implémentant un bloc try/finally et en appelant la méthode Dispose ou DisposeAsync dans le finally.

Important

Le GC ne supprime pas vos objets, car il n’a aucune connaissance de IDisposable.Dispose() ou IAsyncDisposable.DisposeAsync(). Le GC sait uniquement si un objet est finalisable (autrement dit, il définit une méthode Object.Finalize()) et quand le finaliseur de l’objet doit être appelé. Pour plus d’informations, consultez Fonctionnement de la finalisation. Pour plus d’informations sur l’implémentation Dispose et DisposeAsync, consultez :

Les objets qui implémentent System.IDisposable ou System.IAsyncDisposable doivent toujours être supprimés correctement, quelle que soit l’étendue de la variable, sauf indication contraire explicite. Les types qui définissent un finaliseur pour libérer des ressources non managées appellent généralement GC.SuppressFinalize à partir de leur implémentation Dispose ou DisposeAsync. L’appel à SuppressFinalize indique au GC que le finaliseur a déjà été exécuté et que l’objet ne doit pas être promu pour la finalisation.

Instruction using

L’instruction using en C# et l’instruction Using en Visual Basic simplifient le code que vous devez écrire pour nettoyer un objet. L’instruction using obtient une ou plusieurs ressources, exécute les instructions que vous spécifiez, puis supprime automatiquement l’objet. Toutefois, l’instruction using est utile uniquement pour les objets utilisés dans la portée de la méthode dans laquelle elles sont construites.

L’exemple suivant utilise l’instruction using pour créer et libérer un objet System.IO.StreamReader.

using System.IO;

class UsingStatement
{
    static void Main()
    {
        var buffer = new char[50];
        using (StreamReader streamReader = new("file1.txt"))
        {
            int charsRead = 0;
            while (streamReader.Peek() != -1)
            {
                charsRead = streamReader.Read(buffer, 0, buffer.Length);
                //
                // Process characters read.
                //
            }
        }
    }
}
Imports System.IO

Module UsingStatement
    Public Sub Main()
        Dim buffer(49) As Char
        Using streamReader As New StreamReader("File1.txt")
            Dim charsRead As Integer
            Do While streamReader.Peek() <> -1
                charsRead = streamReader.Read(buffer, 0, buffer.Length)
                ' 
                ' Process characters read.
                '
            Loop
        End Using
    End Sub
End Module

Une déclaration using est une autre syntaxe disponible où les accolades sont supprimées, et l’étendue est implicite.

using System.IO;

class UsingDeclaration
{
    static void Main()
    {
        var buffer = new char[50];
        using StreamReader streamReader = new("file1.txt");

        int charsRead = 0;
        while (streamReader.Peek() != -1)
        {
            charsRead = streamReader.Read(buffer, 0, buffer.Length);
            //
            // Process characters read.
            //
        }
    }
}

Bien que la classe StreamReader implémente l’interface IDisposable, ce qui signifie qu’elle utilise une ressource non managée, l’exemple n’appelle pas explicitement la méthode StreamReader.Dispose. Quand le compilateur C# ou Visual Basic rencontre l’instruction using, il émet en langage intermédiaire qui est équivalent au code suivant contenant explicitement un bloc try/finally.

using System.IO;

class TryFinallyGenerated
{
    static void Main()
    {
        var buffer = new char[50];
        StreamReader? streamReader = null;
        try
        {
            streamReader = new StreamReader("file1.txt");
            int charsRead = 0;
            while (streamReader.Peek() != -1)
            {
                charsRead = streamReader.Read(buffer, 0, buffer.Length);
                //
                // Process characters read.
                //
            }
        }
        finally
        {
            // If non-null, call the object's Dispose method.
            streamReader?.Dispose();
        }
    }
}
Imports System.IO

Module TryFinallyGenerated
    Public Sub Main()
        Dim buffer(49) As Char
        Dim streamReader As New StreamReader("File1.txt")
        Try
            Dim charsRead As Integer
            Do While streamReader.Peek() <> -1
                charsRead = streamReader.Read(buffer, 0, buffer.Length)
                ' 
                ' Process characters read.
                '
            Loop
        Finally
            If streamReader IsNot Nothing Then DirectCast(streamReader, IDisposable).Dispose()
        End Try
    End Sub
End Module

L’instruction using en C# vous permet d’acquérir plusieurs ressources dans une seule instruction, ce qui équivaut en interne à des instructions using imbriquées. L'exemple suivant instancie deux objets StreamReader pour lire le contenu de deux fichiers différents.

using System.IO;

class SingleStatementMultiple
{
    static void Main()
    {
        var buffer1 = new char[50];
        var buffer2 = new char[50];

        using StreamReader version1 = new("file1.txt"),
                           version2 = new("file2.txt");

        int charsRead1, charsRead2 = 0;
        while (version1.Peek() != -1 && version2.Peek() != -1)
        {
            charsRead1 = version1.Read(buffer1, 0, buffer1.Length);
            charsRead2 = version2.Read(buffer2, 0, buffer2.Length);
            //
            // Process characters read.
            //
        }
    }
}

Bloc try/finally

Au lieu d’encapsuler un bloc try/finally dans une instruction using, vous pouvez choisir d’implémenter le bloc try/finally directement. Il peut s’agir de votre style personnel en matière de programmation, ou vous pouvez procéder ainsi pour l’une des raisons suivantes :

  • Pour insérer un bloc catch afin de gérer les exceptions levées dans le bloc try. Autrement, toutes les exceptions levées dans l’instruction using ne sont pas gérées.
  • Pour instancier un objet qui implémente IDisposable dont la portée n'est pas locale au bloc dans lequel elle est déclarée.

L'exemple suivant est similaire à l'exemple précédent, mais il utilise un bloc try/catch/finally pour instancier, utiliser et supprimer un objet StreamReader, ainsi que pour gérer les exceptions levées par le constructeur StreamReader et sa méthode ReadToEnd. Le code du bloc finally vérifie que l’objet qui implémente IDisposable n’est pas null avant d’appeler la méthode Dispose. La non-exécution de cette opération peut générer une NullReferenceException au moment de l'exécution.

using System;
using System.Globalization;
using System.IO;

class TryExplicitCatchFinally
{
    static void Main()
    {
        StreamReader? streamReader = null;
        try
        {
            streamReader = new StreamReader("file1.txt");
            string contents = streamReader.ReadToEnd();
            var info = new StringInfo(contents);
            Console.WriteLine($"The file has {info.LengthInTextElements} text elements.");
        }
        catch (FileNotFoundException)
        {
            Console.WriteLine("The file cannot be found.");
        }
        catch (IOException)
        {
            Console.WriteLine("An I/O error has occurred.");
        }
        catch (OutOfMemoryException)
        {
            Console.WriteLine("There is insufficient memory to read the file.");
        }
        finally
        {
            streamReader?.Dispose();
        }
    }
}
Imports System.Globalization
Imports System.IO

Module TryExplicitCatchFinally
    Sub Main()
        Dim streamReader As StreamReader = Nothing
        Try
            streamReader = New StreamReader("file1.txt")
            Dim contents As String = streamReader.ReadToEnd()
            Dim info As StringInfo = New StringInfo(contents)
            Console.WriteLine($"The file has {info.LengthInTextElements} text elements.")
        Catch e As FileNotFoundException
            Console.WriteLine("The file cannot be found.")
        Catch e As IOException
            Console.WriteLine("An I/O error has occurred.")
        Catch e As OutOfMemoryException
            Console.WriteLine("There is insufficient memory to read the file.")
        Finally
            If streamReader IsNot Nothing Then streamReader.Dispose()
        End Try
    End Sub
End Module

Vous pouvez suivre ce modèle de base si vous choisissez d’implémenter (ou que vous devez implémenter) un bloc try/finally, car votre langage de programmation ne prend pas en charge l’instruction using, mais autorise des appels directs à la méthode Dispose.

Membres d’instance IDisposable

Si une classe possède un champ ou une propriété d’instance et que son type implémente IDisposable, la classe doit également implémenter IDisposable. Pour plus d’informations, consultez Implémenter une suppression en cascade.

Voir aussi