Implementar um método Dispose

A implementação do método Dispose se destina principalmente à liberação de recursos não gerenciados. Ao trabalhar com membros de instância que são implementações IDisposable, é comum fazer transmitir chamadas Dispose em cascata. Há motivos adicionais para implementar Dispose, por exemplo, a memória livre alocada, remover um item adicionado a uma coleção ou sinalizar a liberação de um bloqueio adquirido.

O coletor de lixo .NET não alocar nem libera memória não gerenciada. O padrão para o descarte um objeto, conhecido como padrão de descarte, impõe ordem no tempo de vida de um objeto. O padrão de descarte é usado para objetos que implementam a interface IDisposable e é comum ao interagir com identificadores de arquivo e pipe, identificadores do Registro, identificadores de espera ou ponteiros para blocos de memória não gerenciada. Isso ocorre porque o coletor de lixo não é capaz de recuperar objetos não gerenciados.

Para ajudar a garantir que os recursos sejam sempre limpos corretamente, um método Dispose deve ser idempotente, de modo que seja chamado várias vezes sem gerar uma exceção. Além disso, invocações subsequentes de Dispose não devem fazer nada.

O exemplo de código fornecido para o método mostra como a coleta de lixo GC.KeepAlive pode fazer com que um finalizador seja executado enquanto uma referência não gerenciada ao objeto ou seus membros ainda está em uso. Faz sentido usar GC.KeepAlive para tornar o objeto inelegível para coleta de lixo do início da rotina atual até o ponto em que o método é chamado.

Dica

Em relação à injeção de dependência, ao registrar serviços em um IServiceCollection, o tempo de vida do serviço é gerenciado implicitamente em seu nome. O IServiceProvider e o IHost correspondente orquestram a limpeza de recursos. Especificamente, as implementações de IDisposable e IAsyncDisposable são corretamente descartadas no final de seu tempo de vida especificado.

Para mais informações, confira Injeção de dependência no .NET.

Identificadores seguros

Escrever código para o finalizador de um objeto é uma tarefa complexa que poderá causar problemas se não for feito corretamente. Assim, recomendamos que você construa objetos System.Runtime.InteropServices.SafeHandle, em vez de implementar um finalizador.

Um System.Runtime.InteropServices.SafeHandle é um tipo gerenciado abstrato que encapsula um System.IntPtr que identifica um recurso não gerenciado. No Windows, ele pode identificar um identificador durante o Unix, um descritor de arquivo. Ele fornece toda a lógica necessária para garantir que esse recurso seja liberado uma vez, e apenas uma vez, quando o recurso SafeHandle for descartado ou quando todas as referências à instância SafeHandle tiverem sido descartadas e a instância SafeHandle for finalizada.

O System.Runtime.InteropServices.SafeHandle é uma classe base abstrata. As classes derivadas fornecem instâncias específicas para diferentes tipos de identificador. Essas classes derivadas validam quais valores para System.IntPtr são considerados inválidos e como liberar o identificador. Por exemplo, SafeFileHandle deriva de SafeHandle para encapsular IntPtrs que identifica identificadores/descritores de arquivos abertos e substitui seu método SafeHandle.ReleaseHandle() para fechá-lo (por meio da função close no Unix ou da função CloseHandle no Windows). A maioria das APIs em bibliotecas .NET que criam um recurso não gerenciado o encapsulará em um SafeHandle e retornará esse SafeHandle a você conforme necessário, em vez de devolver o ponteiro bruto. Em situações em que você interage com um componente não gerenciado e obtém um recurso IntPtr não gerenciado, você pode criar seu próprio tipo SafeHandle para encapsule-o. Como resultado, poucos tipos não SafeHandle precisam implementar finalizadores; a maioria das implementações de padrões descartáveis acaba encapsulando apenas outros recursos gerenciados, alguns dos quais podem ser SafeHandles.

As seguintes classes derivadas no namespace Microsoft.Win32.SafeHandles fornecem os identificadores seguros:

Dispose() e Dispose(bool)

A interface IDisposable requer a implementação de um único método sem parâmetros, Dispose. Além disso, qualquer classe não selada deve ter um método de sobrecarga adicional Dispose(bool).

As assinaturas do método são:

  • public não virtual (NotOverridable no Visual Basic) (IDisposable.Dispose implementação).
  • protected virtual (Overridable no Visual Basic) Dispose(bool).

O método Dispose()

Como o método public, não virtual (NotOverridable no Visual Basic) e sem parâmetro Dispose é chamado quando não é mais necessário (por um consumidor do tipo), sua finalidade é liberar recursos não gerenciados, realizar limpeza geral e indicar que o finalizador, se houver um, não precisa ser executado. Liberar a memória real associada a um objeto gerenciado é sempre o domínio do coletor de lixo. Por isso, ele tem uma implementação padrão:

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

O método Dispose executa toda a limpeza do objeto, de modo que o coletor de lixo não precisa mais chamar a substituição dos objetos Object.Finalize. Assim, a chamada para o método SuppressFinalize impede que o coletor de lixo execute o finalizador. Se o tipo não possuir um finalizador, a chamada para GC.SuppressFinalize não terá efeito. Observe que a limpeza real é executada pela sobrecarga do método Dispose(bool).

Sobrecarga do método Dispose(bool)

Na sobrecarga, o parâmetro disposing é um Boolean que indica se a chamada de método é proveniente de um método Dispose (seu valor é true) ou de um finalizador (seu valor é false).

protected virtual void Dispose(bool disposing)
{
    if (_disposed)
    {
        return;
    }

    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.

    _disposed = true;
}
Protected Overridable Sub Dispose(disposing As Boolean)
     If disposed Then Exit Sub	

     ' A block that frees unmanaged resources.
     
     If disposing Then
         ' Deterministic call…
         ' A conditional block that frees managed resources.    	
     End If
     
     disposed = True
End Sub

Importante

O parâmetro disposing deve ser false quando chamado de um finalizador e true quando chamado do método IDisposable.Dispose. Em outras palavras, é true quando deterministicamente chamado e false quando não deterministicamente chamado.

O corpo do método consiste em três blocos de código:

  • Um bloco para retorno condicional se o objeto já estiver descartado.

  • Um bloco que libera recursos não gerenciados. Este bloco é executado independentemente do valor do parâmetro disposing.

  • Um bloco condicional que libera recursos gerenciados. Este bloco será executado se o valor de disposing for true. Os recursos gerenciados que ele libera podem incluir:

    • Objetos gerenciados que implementam IDisposable. O bloco condicional pode ser usado para chamar sua implementação de Dispose (descarte em cascata). Se você usou uma classe derivada de System.Runtime.InteropServices.SafeHandle para encapsular o recurso não gerenciado, é necessário chamar a implementação de SafeHandle.Dispose() aqui.

    • Objetos gerenciados que consomem muita memória ou consomem recursos escassos. Atribua grandes referências de objeto gerenciado a null torná-las mais propensas a serem inacessíveis. Isso os libera mais rápido do que se eles fossem recuperados de modo não determinístico.

Se a chamada de método vier de um finalizador, somente o código que libera os recursos não gerenciados deverá ser executado. O implementador é responsável por garantir que o caminho falso não interaja com objetos gerenciados que possam ter sido descartados. Isso é importante porque a ordem na qual o coletor de lixo descarta objetos gerenciados durante a finalização não é determinística.

Transmitir chamadas de descarte em cascata

Se sua classe detém um campo ou propriedade e seu tipo é implementa IDisposable, a própria classe independente também deve implementar IDisposable. Uma classe que instancia uma implementação IDisposable e a armazena como um membro de instância também é responsável por sua limpeza. Isso é feito para ajudar a garantir que os tipos descartáveis referenciados tenham a oportunidade de executar deterministicamente a limpeza por meio do método Dispose. Neste exemplo, a classe é sealed (ou NotInheritable no 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

Dica

Há casos em que talvez você queira executar a verificação null em um finalizador (que inclui o método Dispose(false) invocado por um finalizador), um dos principais motivos é você não saber com certeza se a instância foi totalmente inicializada (por exemplo, uma exceção pode ser gerada em um construtor).

Implementar o padrão de descarte

Todas as classes não seladas (ou classes do Visual Basic não modificadas como NotInheritable) devem ser consideradas uma classe base potencial, pois podem ser herdadas. Se você implementar o padrão de descarte para qualquer classe base potencial, deverá fornecer o seguinte:

  • Uma implementação de Dispose que chame o método Dispose(bool).
  • Um método Dispose(bool) que realiza a limpeza real.
  • Uma classe derivada de SafeHandle que envolva o recurso não gerenciado (recomendado) ou uma substituição para o método Object.Finalize. A classe SafeHandle fornece um finalizador, para que você não precise escrever um por conta própria.

Importante

É possível que uma classe base faça referência apenas a objetos gerenciados e implemente o padrão de descarte. Nesses casos, um finalizador é desnecessário. Um finalizador só será necessário se você fizer referência direta a recursos não gerenciados.

Aqui está um exemplo do padrão geral para implementar o padrão de descarte para uma classe base que usa um identificador seguro.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

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);

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle.Dispose();
            }

            _disposedValue = true;
        }
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class BaseClassWithSafeHandle : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle 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(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
        End If

        disposed = True
    End Sub
End Class

Observação

O exemplo anterior usa um objeto SafeFileHandle para ilustrar o padrão; qualquer objeto derivado de SafeHandle poderia ser usado em vez disso. Observe que o exemplo não cria corretamente uma instância de seu objeto SafeFileHandle.

Aqui está o padrão geral para implementar o padrão de descarte para uma classe base que substitui Object.Finalize.

using System;

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;
        }
    }
}
Class BaseClassWithFinalizer : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' 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(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True
    End Sub

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub
End Class

Dica

No C#, você implementa uma finalização fornecendo um finalizador, não substituindo Object.Finalize. No Visual Basic, você cria um finalizador com Protected Overrides Sub Finalize().

Implementar o padrão de descarte para uma classe derivada

Uma classe derivada de uma classe que implementa a interface IDisposable não deve implementar IDisposable porque a implementação da classe base IDisposable.Dispose é herdada pelas classes derivadas. Em vez disso, para limpar uma classe derivada, você deverá fornecer o seguinte:

  • Um método protected override void Dispose(bool) que substitua o método da classe base e execute o trabalho real de limpeza da classe derivada. Esse método também deve chamar o método base.Dispose(bool) (MyBase.Dispose(bool) no Visual Basic) passando a ele o status de descarte (parâmetro bool disposing) como um argumento.
  • Uma classe derivada de SafeHandle que envolva o recurso não gerenciado (recomendado) ou uma substituição para o método Object.Finalize. A classe SafeHandle fornece um finalizador que o libera de ter que codificar um. Se você fornecer um finalizador, ele deverá chamar a sobrecarga Dispose(bool) com o argumento false.

Aqui está um exemplo do padrão geral para implementar o padrão de descarte para uma classe derivada que usa um identificador seguro:

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

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();
            }

            _disposedValue = true;
        }

        // Call base class implementation.
        base.Dispose(disposing);
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class DerivedClassWithSafeHandle : Inherits BaseClassWithSafeHandle
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
            ' Free any other managed objects here.
            '
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

        ' Call base class implementation.
        MyBase.Dispose(disposing)
    End Sub
End Class

Observação

O exemplo anterior usa um objeto SafeFileHandle para ilustrar o padrão; qualquer objeto derivado de SafeHandle poderia ser usado em vez disso. Observe que o exemplo não cria corretamente uma instância de seu objeto SafeFileHandle.

Aqui está o padrão geral para implementar o padrão de descarte para uma classe derivada que substitui Object.Finalize:

class DerivedClassWithFinalizer : BaseClassWithFinalizer
{
    // To detect redundant calls
    private bool _disposedValue;

    ~DerivedClassWithFinalizer() => this.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);
    }
}
Class DerivedClassWithFinalizer : Inherits BaseClassWithFinalizer
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

        ' Call the base class implementation.
        MyBase.Dispose(disposing)
    End Sub

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub
End Class

Confira também