Implementar um método Dispose

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

O coletor de lixo do .net não aloca ou libera memória não gerenciada. O padrão para descartar um objeto, chamado de padrão Dispose, impõe a ordem no tempo de vida de um objeto. O padrão Dispose é usado para objetos que implementam a IDisposable interface e é comum ao interagir com identificadores de arquivo e pipe, identificadores de registro, identificadores de espera ou ponteiros para blocos de memória não gerenciada. Isso ocorre porque o coletor de lixo não pode recuperar objetos não gerenciados.

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

O exemplo de código fornecido para o GC.KeepAlive método mostra como a coleta de lixo pode fazer com que um finalizador seja executado, enquanto uma referência não gerenciada para o objeto ou seus membros ainda está em uso. Pode fazer sentido utilizar GC.KeepAlive para tornar o objeto inelegível para coleta de lixo do início da rotina atual até o ponto em que esse 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. A IServiceProvider e a IHost limpeza de recurso de orquestra correspondente. Especificamente, as implementações do IDisposable e IAsyncDisposable são descartadas corretamente no final do tempo de vida especificado.

Para obter mais informações, consulte injeção de dependência no .net.

identificadores de Cofre

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. Em Windows ele pode identificar um identificador no UNIX, um descritor de arquivo. Ele fornece toda a lógica necessária para garantir que esse recurso seja liberado apenas uma vez, quando o SafeHandle é Descartado ou quando todas as referências a foram SafeHandle removidas e a SafeHandle instância é 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 do System.IntPtr são considerados inválidos e como realmente liberar o identificador. Por exemplo, SafeFileHandle deriva de SafeHandle para encapsular IntPtrs que identifica identificadores/descritores de arquivo abertos e substitui seu SafeHandle.ReleaseHandle() método para fechá-lo (por meio da close função em UNIX ou CloseHandle Function em Windows). A maioria das APIs em bibliotecas do .NET que criam um recurso não gerenciado o encapsulará em um SafeHandle e retornará isso SafeHandle para você conforme necessário, em vez de redistribuir o ponteiro bruto. Em situações em que você interage com um componente não gerenciado e Obtém um IntPtr para um recurso não gerenciado, você pode criar seu próprio SafeHandle tipo para encapsulá-lo. Como resultado, poucos não SafeHandle tipos precisam implementar finalizadores; a maioria das implementações de padrão descartáveis acaba com o encapsulamento de outros recursos gerenciados, alguns dos quais podem ser SafeHandle s.

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 Dispose(bool) método de sobrecarga adicional.

As assinaturas de método são:

  • publicnão virtual ( NotOverridable em Visual Basic) ( IDisposable.Dispose implementação).
  • protected virtual( Overridable em Visual Basic) Dispose(bool) .

O método Dispose ()

como o public , que não é virtual ( NotOverridable em Visual Basic), o Dispose método sem parâmetros é chamado quando não é mais necessário (por um consumidor do tipo), sua finalidade é liberar recursos não gerenciados, executar a limpeza geral e indicar que o finalizador, se houver, 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 Dispose(bool) sobrecarga do método.

A sobrecarga do método Dispose (bool)

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

protected virtual void Dispose(bool disposing)
{
    if (_disposed)    	
        return;       	
        
    // A block that frees unmanaged resources.	
    
    if(disposing) 
    {
        // Deterministic call…
        // A conditional block that frees managed resources.    	
    }	    
    
    _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 disposing parâmetro deve ser false quando chamado de um finalizador e true quando chamado a partir do IDisposable.Dispose método. Em outras palavras, é true quando chamado de forma determinística e false quando chamado de forma não determinística.

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 Dispose implementação (descarte em cascata). Se você tiver usado uma classe derivada do System.Runtime.InteropServices.SafeHandle para encapsular seu recurso não gerenciado, deverá chamar a SafeHandle.Dispose() implementação aqui.

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

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

Propagar chamadas de Dispose

Se sua classe possui um campo ou propriedade, e seu tipo implementa IDisposable , a própria classe recipiente também deve implementar IDisposable . Uma classe que instancia uma IDisposable implementação e o armazena como um membro de instância também é responsável por sua limpeza. Isso é para ajudar a garantir que os tipos descartáveis referenciados recebam a oportunidade de realizar a limpeza de forma determinística através do Dispose método. Neste exemplo, a classe é sealed (ou NotInheritable em Visual Basic).

using System;

public sealed class Foo : IDisposable
{
    // Model simplified for brevity sake.
    private readonly IDisposable _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

Implementar o padrão Dispose

todas as classes não seladas ou (Visual Basic classes não modificadas como NotInheritable ) devem ser consideradas uma classe base em potencial, porque elas podem ser herdadas. Se você implementar o padrão Dispose para qualquer classe base em potencial, deverá fornecer o seguinte:

  • Uma implementação de Dispose que chame o método Dispose(bool).
  • Um Dispose(bool) método que executa 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 SafeHandle classe fornece um finalizador, portanto, você não precisa escrever um por conta própria.

Importante

É possível que uma classe base referencie apenas objetos gerenciados e implemente o padrão Dispose. 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 (de padrão geral) para implementar o padrão Dispose 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 _disposed = false;

    // 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 (_disposed)
        {
            return;
        }

        if (disposing)
        {
            _safeHandle.Dispose();
        }

        _disposed = 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 _disposed = false;

    ~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 (_disposed)
        {
            return;
        }

        if (disposing)
        {
            // Dispose managed objects that implement IDisposable.
            // Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        }

        // Free unmanaged resources (unmanaged objects).

        _disposed = 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, e não substituindo Object.Finalize . no Visual Basic, você cria um finalizador com Protected Overrides Sub Finalize() .

Implementar o padrão Dispose 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ê fornece o seguinte:

  • Um protected override void Dispose(bool) método que substitui o método da classe base e executa a limpeza real da classe derivada. esse método também deve chamar o base.Dispose(bool) MyBase.Dispose(bool) método (no Visual Basic), passando-o o status de descarte ( bool disposing parâmetro) 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 Dispose(bool) sobrecarga com false argumento.

Aqui está um exemplo (de padrão geral) para implementar o padrão Dispose 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 _disposed = false;

    // Instantiate a SafeHandle instance.
    private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }

        if (disposing)
        {
            _safeHandle.Dispose();
        }

        _disposed = 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
    bool _disposed = false;

    ~DerivedClassWithFinalizer() => this.Dispose(false);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }

        if (disposing)
        {
            // Dispose managed objects that implement IDisposable.
            // Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        }

        // Free unmanaged resources (unmanaged objects).
        _disposed = 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