Implementar um método Dispose
O método Dispose é implementado principalmente para liberar recursos não gerenciados. Ao trabalhar com membros de instância que são implementações de IDisposable, é comum encadear chamadas de Dispose. 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 Dispose é usado para objetos que implementam a interface IDisposable. Esse padrão é comum ao interagir com identificadores de arquivo e pipe, identificadores de registro, identificadores de espera ou ponteiros para blocos de memória não gerida, porque o coletor de lixo não consegue recuperar objetos não geridos.
Para ajudar a assegurar 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 demonstra como a coleta de lixo pode ocasionar a execução de um finalizador enquanto ainda está em uso uma referência não administrada ao objeto ou aos seus membros. 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.
Dica
No que diz respeito à injeção de dependência, ao registrar serviços em um IServiceCollection, o de vida útil do serviço é gerenciado implicitamente em seu nome. O IServiceProvider e o correspondente IHost orquestram 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 a construção de objetos System.Runtime.InteropServices.SafeHandle em vez da implementação de um finalizador.
Um System.Runtime.InteropServices.SafeHandle é um tipo gerenciado abstrato que envolve um System.IntPtr que identifica um recurso não gerenciado. No Windows, pode identificar um identificador, e no Unix, um descritor de ficheiro. 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
foram descartadas e a instância SafeHandle
é 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 SafeHandle
para envolver IntPtrs
que identificam 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 encapsula 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 IntPtr
para um recurso não gerenciado, você pode criar seu próprio tipo de SafeHandle
para envolvê-lo. Como resultado, poucos tipos que não são do tipoSafeHandle
precisam implementar finalizadores. A maioria das implementações de padrões descartáveis acaba apenas por encapsular outros recursos geridos, alguns dos quais podem ser objetos SafeHandle
.
As seguintes classes derivadas no namespace Microsoft.Win32.SafeHandles fornecem identificadores seguros.
Classe | Recursos que detém |
---|---|
SafeFileHandle SafeMemoryMappedFileHandle SafePipeHandle |
Arquivos, arquivos mapeados de memória e canalizações |
SafeMemoryMappedViewHandle | Visualizações de memória |
SafeNCryptKeyHandle SafeNCryptProviderHandle SafeNCryptSecretHandle |
Construções de criptografia |
SafeRegistryHandle | Chaves de registo |
SafeWaitHandle | Manipuladores 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 de 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, 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 método Dispose
executa toda a limpeza de objetos, para que o coletor de lixo não precise mais chamar a substituição de Object.Finalize dos objetos. Portanto, a chamada para o método SuppressFinalize 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 método Dispose(bool)
.
A sobrecarga do método Dispose(bool)
Na sobrecarga, o parâmetro disposing
é um Boolean que indica se a chamada de método vem 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)
{
// Dispose managed state (managed objects).
// ...
}
// Free unmanaged resources.
// ...
_disposed = true;
}
Protected Overridable Sub Dispose(disposing As Boolean)
If disposed Then Exit Sub
If disposing Then
' Free managed resources.
' ...
End If
' Free unmanaged resources.
' ...
disposed = True
End Sub
Importante
O parâmetro disposing
deve ser false
quando chamado a partir de um finalizador e true
quando chamado a partir 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 condicional que libera recursos gerenciados. Este bloco é executado se o valor de
disposing
fortrue
. Os recursos gerenciados que ele libera podem incluir:- Objetos gerenciados que implementam IDisposable. O bloco condicional pode ser usado para chamar sua implementação Dispose (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 implementação SafeHandle.Dispose() aqui.
- Objetos gerenciados que consomem grandes quantidades de memória ou consomem recursos escassos. Atribua grandes referências de objetos gerenciados a
null
para torná-los mais propensos a ficarem inacessíveis. Isto liberta-os mais rapidamente do que se fossem recuperados de forma não determinística.
Um bloco que libera recursos não gerenciados. Este bloco é executado independentemente do valor do parâmetro
disposing
.
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 implementação de IDisposable 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 executar a limpeza de forma determinística através 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 própria ele, o que significa que a classe não cria o objeto, então a classe não precisa implementáIDisposable.
- Há casos em que poderá querer executar a verificação
null
num finalizador (que inclui o métodoDispose(false)
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 implementação Dispose que chama o método
Dispose(bool)
. - Um método
Dispose(bool)
que realiza a verdadeira limpeza. - Uma classe derivada de SafeHandle que encapsula seu recurso não gerenciado (recomendado) ou uma substituição para o método Object.Finalize. A aula SafeHandle 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
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 instancia corretamente 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
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 interface IDisposable não deve implementar IDisposable, porque a implementação de classe base de IDisposable.Dispose é herdada por suas classes derivadas. Em vez disso, para limpar uma classe derivada, forneça o seguinte:
- Um método
protected override void Dispose(bool)
que substitui o método de classe base e executa a limpeza real da classe derivada. Esse método também deve chamar o métodobase.Dispose(bool)
(MyBase.Dispose(bool)
no Visual Basic) passando o estado de descarte (parâmetrobool disposing
) como argumento. - Uma classe derivada de SafeHandle que encapsula seu recurso não gerenciado (recomendado) ou uma substituição para o método Object.Finalize. A classe SafeHandle fornece um finalizador que libera você de ter que codificar um. Caso se forneça um finalizador, ele deve chamar a sobrecarga de
Dispose(bool)
com o argumentofalse
.
Aqui está um exemplo de como implementar o padrão de descarte numa classe derivada que utiliza um safe handle.
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 instancia corretamente 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