Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O método Dispose é implementado principalmente para liberar recursos não gerenciados. Ao trabalhar com membros de instância que são implementações IDisposable, é comum fazer transmitir chamadas Dispose em cascata. Há outros motivos 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 nem libera memória não gerenciada. O padrão para descartar um objeto, conhecido como padrão de descarte, impõe ordem ao 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 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. 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
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 descartadas corretamente no final do ciclo de vida especificado.
Para obter mais informações, consulte Injeção de dependência no .NET.
Transmitir chamadas de descarte em cascata
Se sua classe possui uma instância de outro tipo que implementa IDisposable, a própria classe que contém também deve implementar IDisposable. Normalmente, uma classe que instancia uma IDisposable implementação e a armazena como um membro de instância (ou propriedade) também é responsável por sua limpeza. 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 IDisposable campo ou uma propriedade, mas não a controlar, a classe não precisará implementar IDisposable. Normalmente, uma classe que cria e armazena o IDisposable objeto filho também se torna o proprietário, mas em alguns casos a propriedade pode ser transferida para outro IDisposable tipo.
- Há casos em que talvez você queira executar
null-verificação em um finalizador (que inclui oDispose(false)método 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).
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:
-
publicnão virtual (NotOverridableno Visual Basic) (implementação de IDisposable.Dispose). -
protected virtual(Overridableno 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 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(Boolean)
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 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 condicional que libera recursos gerenciados. Este bloco será executado se o valor de
disposingfortrue. 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ê tiver usado uma classe derivada de System.Runtime.InteropServices.SafeHandle para encapsular seu recurso não gerenciado, você deverá chamar a implementação de SafeHandle.Dispose() aqui.
- Objetos gerenciados que consomem grandes quantidades de memória ou consomem recursos escassos. Atribua grandes referências de objeto gerenciado a
nulltorná-las mais propensas a serem inacessíveis. Isso os libera mais rápido do que se fossem recuperados de forma não determinística.
Um bloco que libera recursos não gerenciados. Esse bloco é executado independentemente do valor do parâmetro
disposing.
Se a chamada de método for proveniente 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 podem 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.
Implementar o padrão de descarte
Todas as classes não lacradas (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 em potencial, deverá adicionar os seguintes métodos à sua classe:
- Uma implementação Dispose que chama o método
Dispose(bool). - Um método
Dispose(bool)que realiza a limpeza real. - Se sua classe lida com recursos não gerenciados, forneça uma substituição ao Object.Finalize método ou encapsule o recurso não gerenciado em um SafeHandle.
Importante
Um finalizador (uma Object.Finalize substituição) só será necessário se você fizer referência direta a recursos não gerenciados. Esse é um cenário altamente avançado que normalmente pode ser evitado:
- Se a classe fizer referência apenas a objetos gerenciados, ainda será possível que a classe implemente o padrão de descarte. Não é necessário implementar um finalizador.
- Se você precisar lidar com recursos não gerenciados, é altamente recomendável encapsular o identificador não gerenciado IntPtr em um SafeHandle. O SafeHandle fornece um finalizador para que você não precise escrever um por conta própria. Para obter mais informações, consulte o parágrafo Manipuladores seguros.
Classe base com recursos gerenciados
Aqui está um exemplo geral de implementação do padrão de descarte para uma classe base que possui apenas recursos gerenciados.
using System;
using System.IO;
public class DisposableBase : IDisposable
{
// Detect redundant Dispose() calls.
private bool _isDisposed;
// Instantiate a disposable object owned by this class.
private Stream? _managedResource = new MemoryStream();
// 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 (!_isDisposed)
{
_isDisposed = true;
if (disposing)
{
// Dispose managed state.
_managedResource?.Dispose();
_managedResource = null;
}
}
}
}
Imports System.IO
Public Class DisposableBase
Implements IDisposable
' Detect redundant Dispose() calls.
Private _isDisposed As Boolean
' Instantiate a disposable object owned by this class.
Private _managedResource As Stream = New MemoryStream()
' 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 Not _isDisposed Then
_isDisposed = True
If disposing Then
' Dispose managed state.
_managedResource?.Dispose()
_managedResource = Nothing
End If
End If
End Sub
End Class
Nota
O exemplo anterior usa um objeto fictícioMemoryStream para ilustrar o padrão. Qualquer IDisposable pode ser usado em substituição.
Classe base com recursos não gerenciados
Aqui está um exemplo para implementar o padrão de descarte para uma classe base que substitui Object.Finalize para limpar recursos não gerenciados que possui. O exemplo também demonstra uma maneira de implementar Dispose(bool) de maneira thread-safe. A sincronização pode ser crítica ao lidar com recursos não gerenciados em um aplicativo com vários threads. Conforme mencionado anteriormente, este é um cenário avançado.
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
public class DisposableBaseWithFinalizer : IDisposable
{
// Detect redundant Dispose() calls in a thread-safe manner.
// _isDisposed == 0 means Dispose(bool) has not been called yet.
// _isDisposed == 1 means Dispose(bool) has been already called.
private int _isDisposed;
// Instantiate a disposable object owned by this class.
private Stream? _managedResource = new MemoryStream();
// A pointer to 10 bytes allocated on the unmanaged heap.
private IntPtr _unmanagedResource = Marshal.AllocHGlobal(10);
~DisposableBaseWithFinalizer() => 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)
{
// In case _isDisposed is 0, atomically set it to 1.
// Enter the branch only if the original value is 0.
if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
if (disposing)
{
_managedResource?.Dispose();
_managedResource = null;
}
Marshal.FreeHGlobal(_unmanagedResource);
}
}
}
Imports System
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Threading
Public Class DisposableBaseWithFinalizer
Implements IDisposable
' Detect redundant Dispose() calls in a thread-safe manner.
' _isDisposed == 0 means Dispose(bool) has not been called yet.
' _isDisposed == 1 means Dispose(bool) has been already called.
Private _isDisposed As Integer
' Instantiate a disposable object owned by this class.
Private _managedResource As Stream = New MemoryStream()
' A pointer to 10 bytes allocated on the unmanaged heap.
Private _unmanagedResource As IntPtr = Marshal.AllocHGlobal(10)
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(disposing As Boolean)
' In case _isDisposed is 0, atomically set it to 1.
' Enter the branch only if the original value is 0.
If Interlocked.CompareExchange(_isDisposed, 1, 0) = 0 Then
If disposing Then
_managedResource?.Dispose()
_managedResource = Nothing
End If
Marshal.FreeHGlobal(_unmanagedResource)
End If
End Sub
End Class
Nota
- O exemplo anterior usa AllocHGlobal para alocar 10 bytes no heap não gerenciado no construtor e liberar o buffer
Dispose(bool)chamando FreeHGlobal. Essa é uma alocação fictícia para fins ilustrativos. - Novamente, é recomendável evitar a implementação de um finalizador. Consulte Implementar o padrão de descarte usando um identificador seguro personalizado para um equivalente do exemplo anterior que delega a finalização e a sincronização não determinísticas para SafeHandle.
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, pois a implementação da classe base de IDisposable.Dispose é herdada por suas classes derivadas. Em vez disso, para limpar uma classe derivada, você fornece 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 a ele o status de descarte (parâmetrobool disposing) como um argumento. - Uma classe derivada de SafeHandle que encapsule seu recurso não gerenciado (recomendado) ou uma substituição do método Object.Finalize. A classe SafeHandle fornece um finalizador que libera você 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 System.IO;
public class DisposableDerived : DisposableBase
{
// To detect redundant calls
private bool _isDisposed;
// Instantiate a disposable object owned by this class.
private Stream? _managedResource = new MemoryStream();
// Protected implementation of Dispose pattern.
protected override void Dispose(bool disposing)
{
if (!_isDisposed)
{
_isDisposed = true;
if (disposing)
{
_managedResource?.Dispose();
_managedResource = null;
}
}
// Call base class implementation.
base.Dispose(disposing);
}
}
Imports System.IO
Public Class DisposableDerived
Inherits DisposableBase
' To detect redundant calls
Private _isDisposed As Boolean
' Instantiate a disposable object owned by this class.
Private _managedResource As Stream = New MemoryStream()
' Protected implementation of Dispose pattern.
Protected Overrides Sub Dispose(disposing As Boolean)
If Not _isDisposed Then
_isDisposed = True
If disposing Then
_managedResource?.Dispose()
_managedResource = Nothing
End If
End If
' Call base class implementation.
MyBase.Dispose(disposing)
End Sub
End Class
Nota
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 o objeto SafeFileHandle.
Este é o padrão geral para implementar o padrão de descarte para uma classe derivada que substitui Object.Finalize:
using System.Threading;
public class DisposableDerivedWithFinalizer : DisposableBaseWithFinalizer
{
// Detect redundant Dispose() calls in a thread-safe manner.
// _isDisposed == 0 means Dispose(bool) has not been called yet.
// _isDisposed == 1 means Dispose(bool) has been already called.
private int _isDisposed;
~DisposableDerivedWithFinalizer() => Dispose(false);
// Protected implementation of Dispose pattern.
protected override void Dispose(bool disposing)
{
// In case _isDisposed is 0, atomically set it to 1.
// Enter the branch only if the original value is 0.
if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
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.
}
// Call the base class implementation.
base.Dispose(disposing);
}
}
Imports System.Threading
Public Class DisposableDerivedWithFinalizer
Inherits DisposableBaseWithFinalizer
' Detect redundant Dispose() calls in a thread-safe manner.
' _isDisposed == 0 means Dispose(bool) has not been called yet.
' _isDisposed == 1 means Dispose(bool) has been already called.
Private _isDisposed As Integer
Protected Overrides Sub Finalize()
Dispose(False)
End Sub
' Protected implementation of Dispose pattern.
Protected Overrides Sub Dispose(disposing As Boolean)
' In case _isDisposed is 0, atomically set it to 1.
' Enter the branch only if the original value is 0.
If Interlocked.CompareExchange(_isDisposed, 1, 0) = 0 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.
End If
' Call the base class implementation.
MyBase.Dispose(disposing)
End Sub
End Class
Identificadores seguros
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ê crie System.Runtime.InteropServices.SafeHandle objetos ao invés 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 somente uma vez, seja quando o SafeHandle for liberado ou quando todas as referências à SafeHandle tiverem sido removidas 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 o System.IntPtr são considerados inválidos e como realmente liberar a alça. Por exemplo, SafeFileHandle deriva de SafeHandle para encapsular IntPtrs que identifica handles de arquivo abertos e descritores e substitui seu método SafeHandle.ReleaseHandle() para fechá-lo (pela função close no Unix ou a 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 o recurso encapsulado 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 do tipoSafeHandle precisam implementar finalizadores. A maioria das implementações de padrões descartáveis acaba encapsulando apenas outros recursos gerenciados, alguns dos quais podem ser SafeHandle objetos.
Implementar o padrão de descarte usando um identificador seguro personalizado
O código a seguir demonstra como lidar com recursos não gerenciados implementando um SafeHandle.
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
// Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle.
class LocalAllocHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private LocalAllocHandle() : base(ownsHandle: true) { }
// No need to implement a finalizer - SafeHandle's finalizer will call ReleaseHandle for you.
protected override bool ReleaseHandle()
{
Marshal.FreeHGlobal(handle);
return true;
}
// Allocate bytes with Marshal.AllocHGlobal() and wrap the result into a SafeHandle.
public static LocalAllocHandle Allocate(int numberOfBytes)
{
IntPtr nativeHandle = Marshal.AllocHGlobal(numberOfBytes);
LocalAllocHandle safeHandle = new LocalAllocHandle();
safeHandle.SetHandle(nativeHandle);
return safeHandle;
}
}
public class DisposableBaseWithSafeHandle : IDisposable
{
// Detect redundant Dispose() calls.
private bool _isDisposed;
// Managed disposable objects owned by this class
private LocalAllocHandle? _safeHandle = LocalAllocHandle.Allocate(10);
private Stream? _otherUnmanagedResource = new MemoryStream();
// 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 (!_isDisposed)
{
_isDisposed = true;
if (disposing)
{
// Dispose managed state.
_otherUnmanagedResource?.Dispose();
_safeHandle?.Dispose();
_otherUnmanagedResource = null;
_safeHandle = null;
}
}
}
}
Imports System
Imports System.IO
Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles
' Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle.
Public Class LocalAllocHandle
Inherits SafeHandleZeroOrMinusOneIsInvalid
Private Sub New()
MyBase.New(True)
End Sub
' No need to implement a finalizer - SafeHandle's finalizer will call ReleaseHandle for you.
Protected Overrides Function ReleaseHandle() As Boolean
Marshal.FreeHGlobal(handle)
Return True
End Function
' Allocate bytes with Marshal.AllocHGlobal() and wrap the result into a SafeHandle.
Public Shared Function Allocate(numberOfBytes As Integer) As LocalAllocHandle
Dim nativeHandle As IntPtr = Marshal.AllocHGlobal(numberOfBytes)
Dim safeHandle As New LocalAllocHandle()
safeHandle.SetHandle(nativeHandle)
Return safeHandle
End Function
End Class
Public Class DisposableBaseWithSafeHandle
Implements IDisposable
' Detect redundant Dispose() calls.
Private _isDisposed As Boolean
' Managed disposable objects owned by this class
Private _safeHandle As LocalAllocHandle = LocalAllocHandle.Allocate(10)
Private _otherUnmanagedResource As Stream = New MemoryStream()
' 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 Not _isDisposed Then
_isDisposed = True
If disposing Then
' Dispose managed state.
_otherUnmanagedResource?.Dispose()
_safeHandle?.Dispose()
_otherUnmanagedResource = Nothing
_safeHandle = Nothing
End If
End If
End Sub
End Class
Nota
O comportamento da DisposableBaseWithSafeHandle classe é equivalente ao comportamento da DisposableBaseWithFinalizer classe em um exemplo anterior, no entanto, a abordagem demonstrada aqui é mais segura:
- Não é necessário implementar um finalizador, pois SafeHandle cuidará da finalização.
- Não há necessidade de sincronização para garantir a segurança do thread. Embora haja uma condição de corrida na
Disposeimplementação deDisposableBaseWithSafeHandle, SafeHandle garante que SafeHandle.ReleaseHandle é chamada apenas uma vez.
Manipuladores seguros integrados no .NET
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 |
Construções de criptografia |
| SafeRegistryHandle | Chaves do Registro |
| SafeWaitHandle | Identificadores de espera |