Usando objetos que implementam IDisposable

O GC (coletor de lixo) do Common Language Runtime recupera a memória usada por objetos gerenciados. Normalmente, os tipos que usam recursos não gerenciamento implementam a interface ou para permitir que os recursos não gerenciamento IDisposable IAsyncDisposable sejam recuperados. Quando terminar de usar um objeto que implementa , chame a implementação do objeto IDisposable Dispose ou para executar DisposeAsync explicitamente a limpeza. É possível fazer isso de duas formas:

  • Com a instrução using ou declaração C# ( Using em Visual Basic).
  • Implementando um try/finally bloco e chamando o método ou no Dispose DisposeAsync finally .

Importante

O GC não descarta seus objetos, pois não tem conhecimento de ou IDisposable.Dispose() IAsyncDisposable.DisposeAsync() . O GC só sabe se um objeto é finalizável (ou seja, define um método) e quando o finalizador do Object.Finalize() objeto precisa ser chamado. Para obter mais informações, consulte Como a finalização funciona. Para obter detalhes adicionais sobre como Dispose implementar e DisposeAsync , consulte:

Objetos que System.IDisposable implementam ou System.IAsyncDisposable sempre devem ser descartados corretamente, independentemente do scoping de variável, a menos que explicitamente declarado de outra forma. Tipos que definem um finalizador para liberar recursos nãomanagedos geralmente chamam GC.SuppressFinalize de sua implementação ou Dispose DisposeAsync . Chamar indica ao GC que o finalizador já foi executado e o objeto não SuppressFinalize deve ser promovido para finalização.

A instrução using

A using instrução em C# e a Using instrução em Visual Basic simplificam o código que você deve escrever para limpar um objeto. A instrução using obtém um ou mais recursos, executa as instruções que você especifica e descarta o objeto automaticamente. Entretanto, a instrução using é útil apenas para os objetos que são usados no escopo do método no qual eles são criados.

O exemplo a seguir usa a instrução using para criar e liberar um objeto 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

Com o C# 8, uma declaração é uma sintaxe alternativa disponível na qual as chaves são removidas e o scoping é implícito. using

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

Embora a classe implemente a interface , que indica que ela usa um recurso não gerenciamento, o exemplo não StreamReader IDisposable chama explicitamente o método StreamReader.Dispose . Quando o compilador do C# ou do Visual Basic encontra a instrução using, ele emite a IL (linguagem intermediária) que equivale ao seguinte código que contém explicitamente um bloco 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

A instrução using do C# também permite que você adquira vários recursos em uma única instrução, o que é internamente equivalente a instruções using aninhadas. O exemplo a seguir cria instancia dois objetos StreamReader para ler o conteúdo de dois arquivos diferentes.

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

Bloco Try/finally

Em vez de incluir um bloco try/finally em uma instrução using, você pode optar por implementar diretamente o bloco try/finally. Pode ser seu estilo de codificação pessoal ou talvez você queira fazer isso por um dos seguintes motivos:

  • Para incluir um catch bloco para lidar com exceções lançadas no try bloco. Caso contrário, todas as exceções lançadas dentro using da instrução não serão acionadas.
  • Para criar uma instância de um objeto que implementa IDisposable cujo escopo não é local para o bloco dentro do qual é declarado.

O exemplo a seguir é semelhante ao exemplo anterior, exceto que ele usa um bloco try/catch/finally para criar uma instância, usar e descartar um objeto StreamReader e também para manipular todas as exceções lançadas pelo construtor StreamReader e pelo método ReadToEnd. O código no finally bloco verifica se o objeto que implementa não é antes de chamar o método IDisposable null Dispose . Não fazer isso poderá levar a uma exceção de NullReferenceException em tempo de execução.

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

Você poderá seguir esse padrão básico se optar por implementar ou precisar implementar um bloco try/finally, pois a linguagem de programação não oferece suporte a uma instrução using, mas permite chamadas diretas para o método Dispose.

Membros da instância IDisposable

Se uma classe contém uma implementação como um membro de instância, um campo ou IDisposable uma propriedade, a classe também deve implementar IDisposable . Para obter mais informações, consulte implementar um descarte em cascata.

Confira também