Uso di oggetti che implementano IDisposable

Il Garbage Collector (GC) di Common Language Runtime recupera la memoria usata dagli oggetti gestiti. In genere, i tipi che usano risorse non gestite implementano l'interfaccia IDisposable o IAsyncDisposable per consentire il recupero delle risorse non gestite. Al termine dell'uso di un oggetto che implementa IDisposable, si chiama Dispose dell l'oggetto o l'implementazione DisposeAsync per eseguire in modo esplicito la pulizia. È possibile effettuare questa operazione in uno dei due modi seguenti:

  • Con l'istruzione o la dichiarazione using C# (Using in Visual Basic).
  • Implementando un blocco di try/finally e chiamando il metodo Dispose o DisposeAsync in finally.

Importante

Il GC non elimina gli oggetti, perché non ha alcuna conoscenza di IDisposable.Dispose() o IAsyncDisposable.DisposeAsync(). Il GC sa solo se un oggetto è finalizzabile (ovvero, definisce un metodo Object.Finalize()) e quando è necessario chiamare il finalizzatore dell'oggetto. Per altre informazioni, vedere Funzionamento della finalizzazione. Per altri dettagli sull'implementazione di Dispose e DisposeAsync, vedere:

Gli oggetti che implementano System.IDisposable o System.IAsyncDisposable devono essere sempre eliminati correttamente, indipendentemente dall'ambito delle variabili, a meno che non diversamente specificato in modo esplicito. I tipi che definiscono un finalizzatore per rilasciare risorse non gestite in genere chiamano GC.SuppressFinalize dalla rispettiva implementazione Dispose o DisposeAsync. Chiamando SuppressFinalize indica al GC che il finalizzatore è già stato eseguito e che l'oggetto non deve essere alzato di livello per la finalizzazione.

Istruzione Using

L’istruzione using in C# e l'istruzione Using in Visual Basic semplificano il codice da scrivere per pulire un oggetto. L'istruzione using ottiene una o più risorse, esegue le istruzioni specificate ed elimina l'oggetto in modo automatico. L'istruzione using è comunque utile solo per gli oggetti usati nell'ambito del metodo in cui vengono costruiti.

Nell'esempio seguente viene utilizzata l'istruzione using per creare e rilasciare un oggetto 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

Una dichiarazione using è una sintassi alternativa disponibile in cui vengono rimosse le parentesi graffe e l'ambito è implicito.

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.
            //
        }
    }
}

Anche se la classe StreamReader implementa l'interfaccia IDisposable, che indica che usa una risorsa non gestita, l'esempio non chiama in modo esplicito il metodo StreamReader.Dispose. Quando nel compilatore C# o Visual Basic viene rilevata l'istruzione using, viene generato il linguaggio intermedio (IL) equivalente al codice seguente, che contiene un blocco try/finally in modo esplicito.

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'istruzione using C# è consente anche di acquisire più risorse in un'unica istruzione, che equivale internamente all'uso di più istruzioni using annidate. Nell'esempio seguente viene creata l'istanza di due oggetti StreamReader per leggere il contenuto di due diversi file.

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.
            //
        }
    }
}

Blocco try/finally

Anziché eseguire il wrapping di un blocco try/finally in un'istruzione using, è possibile implementare direttamente il blocco try/finally. La scelta può essere espressione dello stile di codifica personale oppure essere dovuta a uno dei seguenti motivi:

  • Includere un blocco catch per gestire eventuali eccezioni generate nel blocco try. In caso contrario, tutte le eccezioni generate all'interno dell'istruzione using non vengono gestite.
  • Creare un'istanza di un oggetto che implementa IDisposable il cui ambito non è locale rispetto al blocco in cui viene dichiarato.

L'esempio seguente è simile a quello precedente, con la differenza che in questo viene usato un blocco try/catch/finally per creare un'istanza di un oggetto StreamReader, utilizzarla ed eliminarla e per gestire le eccezioni generate dal costruttore StreamReader e dal relativo metodo ReadToEnd. Il codice nel blocco finally controlla che l'oggetto che implementa IDisposable non sia null prima di chiamare il metodo Dispose. L'omissione di tale controllo può provocare un'eccezione NullReferenceException in fase di esecuzione.

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

È possibile usare questo modello di base se si decide di implementare o è necessario implementare un blocco try/finally, poiché il linguaggio di programmazione non supporta un'istruzione using ma consente chiamate dirette al metodo Dispose.

Membri dell'istanza IDisposable

Se una classe è proprietaria di un campo di istanza o di una proprietà e il relativo tipo implementa IDisposable, la classe deve implementare anche IDisposable. Per altre informazioni, vedere Implementare un'eliminazione a catena.

Vedi anche