Partilhar via


Implementar um método Dispose

O Dispose método é implementado principalmente para liberar recursos não gerenciados. Ao trabalhar com membros de instância que são IDisposable implementações, é comum fazer chamadas em Dispose cascata. Existem outras razões para implementar Dispose, por exemplo, 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 .NET não aloca nem libera memória não gerenciada. O padrão para descartar um objeto, referido como o padrão de descarte, impõe ordem sobre o tempo de vida de um objeto. O padrão de descarte é usado para objetos que implementam a IDisposable interface. Esse padrão é comum ao interagir com identificadores de arquivo e pipe, identificadores de registro, alças de espera ou ponteiros para blocos de memória não gerenciada, porque o coletor de lixo não consegue recuperar objetos não gerenciados.

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

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

Gorjeta

Com 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 e correspondente IServiceProviderIHost orquestrar a limpeza de recursos. Especificamente, implementações de IDisposable e IAsyncDisposable são adequadamente descartadas no final de sua vida útil especificada.

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

Pegas seguras

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

A 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 uma única vez, seja quando o SafeHandle é descartado ou quando todas as referências ao SafeHandle recurso foram descartadas 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 manipulador. Essas classes derivadas validam quais valores para o System.IntPtr são considerados inválidos e como realmente liberar o identificador. Por exemplo, SafeFileHandle deriva de to wrap IntPtrs que identifica identificadores/descritores de SafeHandle arquivos abertos e substitui seu SafeHandle.ReleaseHandle() método para fechá-lo (através da close função no Unix ou CloseHandle função no Windows). A maioria das APIs em bibliotecas .NET que criam um recurso não gerenciado o encapsula em um SafeHandle e retorna 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 IntPtr para um recurso não gerenciado, você pode criar seu próprio SafeHandle tipo para envolvê-lo. Como resultado, poucos não-tiposSafeHandle precisam implementar finalizadores. A maioria das implementações de padrão descartáveis acaba apenas envolvendo outros recursos gerenciados, alguns dos quais podem ser SafeHandle objetos.

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

Classe Recursos que detém
SafeFileHandle
SafeMemoryMappedFileHandle
SafePipeHandle
Arquivos, arquivos mapeados de memória e pipes
SafeMemoryMappedViewHandle Visualizações de memória
SafeNCryptKeyHandle
SafeNCryptProviderHandle
SafeNCryptSecretHandle
Construções de criptografia
SafeRegistryHandle Chaves do Registo
SafeWaitHandle Alças de espera

Descartar() e Descartar(bool)

A IDisposable interface 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.

As assinaturas do método são:

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

O método Dispose()

Como o publicmétodo , não virtual (NotOverridable no Visual Basic), sem Dispose parâmetros é chamado quando não é mais necessário (por um consumidor do tipo), sua finalidade é liberar recursos não gerenciados, executar 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, 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 Dispose método executa toda a limpeza de objetos, para que o coletor de lixo não precise mais chamar a substituição dos Object.Finalize objetos. Portanto, a chamada para o SuppressFinalize método impede que o coletor de lixo execute o finalizador. Se o tipo não tiver finalizador, a chamada para GC.SuppressFinalize não terá efeito. A limpeza real é realizada pela sobrecarga do Dispose(bool) método.

A sobrecarga do método Dispose(bool)

Na sobrecarga, o disposing parâmetro é um Boolean que indica se a chamada de 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;
    }

    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 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 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 disposing parâmetro.

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

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

    • Objetos gerenciados que consomem grandes quantidades de memória ou consomem recursos escassos. Atribua grandes referências de objetos gerenciados para null torná-los mais propensos a ficarem inacessíveis. Isto liberta-os 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 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.

Chamadas de eliminação em cascata

Se sua classe possui um campo ou propriedade e seu tipo implementa IDisposable, a própria classe que contém também deve implementar IDisposable. Uma classe que instancia uma IDisposable implementação e a armazena como um membro da instância também é responsável por sua limpeza. Isso ajuda a garantir que os tipos descartáveis referenciados tenham a oportunidade de realizar a limpeza deterministicamente através do Dispose método. 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

Gorjeta

  • Se sua classe tem um IDisposable campo ou propriedade, mas não é proprietária dele, o que significa que a classe não cria o objeto, então a classe não precisa implementar IDisposable.
  • Há casos em que você pode querer executar null-checking em um finalizador (que inclui o Dispose(false) método invocado por um finalizador). Um dos principais motivos é se você não tiver certeza se a instância foi totalmente inicializada (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, porque elas podem ser herdadas. Se você implementar o padrão de descarte para qualquer classe base potencial, deverá fornecer o seguinte:

  • Uma Dispose implementação que chama o Dispose(bool) método.
  • Um Dispose(bool) método que executa a limpeza real.
  • Uma classe derivada SafeHandle disso encapsula seu recurso não gerenciado (recomendado) ou uma substituição para o Object.Finalize método. A SafeHandle aula fornece um finalizador, para que você não precise escrever um sozinho.

Importante

É possível que uma classe base faça referência apenas a objetos gerenciados e implemente o padrão de descarte. Nestes casos, um finalizador é desnecessário. Um finalizador só é 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

Nota

O exemplo anterior usa um SafeFileHandle objeto para ilustrar o padrão, qualquer objeto derivado poderia ser usado em vez disso SafeHandle . Observe que o exemplo não instancia corretamente seu SafeFileHandle objeto.

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

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

Gorjeta

Em 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 IDisposable interface não deve implementar IDisposable, porque a implementação da classe base de IDisposable.Dispose é herdada por suas classes derivadas. Em vez disso, para limpar uma classe derivada, forneça o seguinte:

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

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

Nota

O exemplo anterior usa um SafeFileHandle objeto para ilustrar o padrão, qualquer objeto derivado poderia ser usado em vez disso SafeHandle . Observe que o exemplo não instancia corretamente seu SafeFileHandle objeto.

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

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

Consulte também