Partager via


Utilisation d’objets qui implémentent IDisposable

Le garbage collector (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 restauration des ressources non managées. Lorsque vous avez terminé d'utiliser un objet qui implémente IDisposable, vous appelez l'implémentation Dispose ou DisposeAsync de l'objet pour effectuer explicitement le nettoyage. Vous pouvez effectuer cette opération de deux manières :

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

Importante

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 Object.Finalize() méthode) et quand le finaliseur de l’objet doit être appelé. Pour plus d’informations, consultez Le 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 correctement supprimés, quel que soit l’étendue des variables, sauf indication explicite. Les types qui définissent un finaliseur pour libérer des ressources non managées appellent GC.SuppressFinalize généralement à partir de leur Dispose ou DisposeAsync implémentation. 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’instructionusing en C# et l’instructionUsing 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 et 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 ils sont construits.

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

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 syntaxe alternative disponible lorsque les accolades sont supprimées et que 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 StreamReader classe implémente l’interface IDisposable , ce qui indique qu’elle utilise une ressource non managée, l’exemple n’appelle pas explicitement la StreamReader.Dispose méthode. Lorsque le compilateur C# ou Visual Basic rencontre l’instruction using , il émet un langage intermédiaire (IL) équivalent au code suivant qui contient explicitement un try/finally bloc.

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 C# using vous permet également d’acquérir plusieurs ressources dans une seule instruction, qui est en interne équivalente aux instructions imbriquées using . L’exemple suivant instancie deux StreamReader objets 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 try/finally bloc dans une using instruction, vous pouvez choisir d’implémenter le try/finally bloc directement. Il peut s’agir de votre style de codage personnel, ou vous pouvez le faire 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 l’étendue n’est pas locale au niveau du bloc dans lequel elle est déclarée.

L’exemple suivant est similaire à l’exemple précédent, sauf qu’il utilise un try/catch/finally bloc pour instancier, utiliser et supprimer un StreamReader objet, et pour gérer les exceptions levées par le StreamReader constructeur et sa ReadToEnd méthode. Le code du finally bloc vérifie que l’objet qui implémente IDisposable n’est pas null avant d’appeler la Dispose méthode. L’échec de cette opération peut entraîner une NullReferenceException exception 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 devez implémenter un try/finally bloc, car votre langage de programmation ne prend pas en charge une using instruction, mais autorise les appels directs à la Dispose méthode.

Membres d’instance IDisposable

Si une classe possède un champ d’instance ou une propriété 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