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
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 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étodoDispose(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étodobase.Dispose(bool)
(MyBase.Dispose(bool)
no Visual Basic) passando a ele o status de descarte (parâmetrobool 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 argumentofalse
.
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