Implementar um método Dispose

O método Dispose é implementado principalmente para lançar recursos não gerenciados. Ao trabalhar com membros de instância que são implementações IDisposable, é comum fazer transmitir chamadas Dispose em cascata. Existem outras razões para implementar Dispose, por exemplo, para liberar a memória que foi alocada, remover um item que foi adicionado a uma coleção ou sinalizar a liberação de um bloqueio adquirido.

O coletor de lixo .NET não aloca ou 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. Esse padrão é 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, pois o coletor de lixo não consegue recuperar objetos não gerenciados.

Para ajudar a garantir que os recursos sejam sempre limpos adequadamente, um método Dispose deve ser idempotente, de modo que possa ser 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 GC.KeepAlive mostra como a coleta de lixo pode fazer com que um finalizador seja executado enquanto uma referência não gerenciada ao objeto ou aos membros dele 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 e, no Unix, um descritor de arquivo. O SafeHandle fornece toda a lógica necessária para garantir que esse recurso seja liberado 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 das bibliotecas .NET que criam um recurso não gerenciado o envolve em um SafeHandle e retorna esse SafeHandle para 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 que não sã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 objetos SafeHandle.

As classes derivadas a seguir no namespace Microsoft.Win32.SafeHandles fornecem identificadores seguros.

Classe Recursos que ele contém
SafeFileHandle
SafeMemoryMappedFileHandle
SafePipeHandle
Arquivos, arquivos mapeados de memória e pipes
SafeMemoryMappedViewHandle Exibições de memória
SafeNCryptKeyHandle
SafeNCryptProviderHandle
SafeNCryptSecretHandle
Constructos de criptografia
SafeRegistryHandle Chaves do Registro
SafeWaitHandle Identificadores de espera

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 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), sem parâmetros 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 estiver presente, 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. A limpeza real é realizada 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 rapidamente do que se fossem recuperados de forma não determinística.

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 em que 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 o tipo dela implementa IDisposable, a própria classe recipiente também deve implementar IDisposable. Uma classe que cria uma instância de uma implementação IDisposable e a armazena como um membro de instância também é responsável pela limpeza dela. Isso ajuda a garantir que os tipos descartáveis referenciados tenham a oportunidade de executar deterministicamente a limpeza por meio do método Dispose. No exemplo a seguir, 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

  • Se sua classe tiver um campo ou propriedade IDisposable, mas não for proprietária dela, ou seja, a classe não cria o objeto, ela não precisa implementar IDisposable.
  • Há casos em que talvez você queira executar uma verificação de null em um finalizador (que inclui o método Dispose(false) invocado por um finalizador). Um dos motivos principais para isso é se não tiver certeza se a instância foi inicializada completamente (por exemplo, uma exceção pode ser lançada 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 geral de implementação do padrão de descarte para uma classe base que usa um identificador seguro.

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

public 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);
        GC.SuppressFinalize(this);
    }

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

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

Public Class BaseClassWithSafeHandle
    Implements IDisposable

    ' To detect redundant calls
    Private _disposedValue As Boolean

    ' Instantiate a SafeHandle instance.
    Private _safeHandle 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(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                _safeHandle?.Dispose()
                _safeHandle = Nothing
            End If

            _disposedValue = True
        End If
    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;

public 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;
        }
    }
}
Public Class BaseClassWithFinalizer
    Implements IDisposable

    ' To detect redundant calls
    Private _disposedValue As Boolean

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

    ' 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(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                ' TODO: dispose managed state (managed objects)
            End If

            ' TODO free unmanaged resources (unmanaged objects) And override finalizer
            ' TODO: set large fields to null
            _disposedValue = True
        End If
    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;

public 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();
                _safeHandle = null;
            }

            _disposedValue = true;
        }

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

Public Class DerivedClassWithSafeHandle
    Inherits BaseClassWithSafeHandle

    ' To detect redundant calls
    Private _disposedValue As Boolean

    ' Instantiate a SafeHandle instance.
    Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                _safeHandle?.Dispose()
                _safeHandle = Nothing
            End If

            _disposedValue = True
        End If

        ' 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:

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

    ~DerivedClassWithFinalizer() => 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);
    }
}
Public Class DerivedClassWithFinalizer
    Inherits BaseClassWithFinalizer

    ' To detect redundant calls
    Private _disposedValue As Boolean

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

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                ' TODO: dispose managed state (managed objects).
            End If

            ' TODO free unmanaged resources (unmanaged objects) And override a finalizer below.
            ' TODO: set large fields to null.
            _disposedValue = True
        End If

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

Confira também