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 virtual
Overridable
( no Visual Basic)Dispose(bool)
.
O método Dispose()
Como o public
mé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
fortrue
. 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 oDispose(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 obase.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 comfalse
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