Implementación de un método Dispose
El método Dispose se implementa principalmente para liberar recursos no administrados. Al trabajar con miembros de instancia que son implementaciones de IDisposable, es habitual hacer llamadas de Dispose en cascada. Hay otras razones para implementar Dispose, por ejemplo, para liberar memoria asignada, quitar un elemento que se agregó a una colección o indicar la liberación de un bloqueo que se adquirió.
El recolector de elementos no utilizados de .NET no asigna ni libera memoria no administrada. El patrón para desechar un objeto, denominado patrón dispose, impone el orden de duración de un objeto. El patrón dispose se usa para objetos que implementan la interfaz IDisposable. Este patrón es común al interactuar con identificadores de archivo y canalización, identificadores de registro, identificadores de espera o punteros a bloques de memoria sin administrar, ya que el recolector de elementos no utilizados no puede reclamar objetos no administrados.
Para asegurarse de que los recursos se limpien siempre correctamente, un método Dispose debe ser idempotente, de manera que sea invocable varias veces sin que se produzca una excepción. Además, las invocaciones posteriores de Dispose no deberían hacer nada.
El ejemplo de código proporcionado para el método GC.KeepAlive muestra cómo la recolección de elementos no utilizados puede hacer que un finalizador se ejecute mientras una referencia no administrada al objeto o a sus miembros todavía está en uso. Puede tener sentido utilizar GC.KeepAlive para que el objeto sea ineligible para la recolección de basura, desde el inicio de la rutina actual hasta el punto en que se llama a este método.
Sugerencia
Con respecto a la inserción de dependencias, al registrar servicios en IServiceCollection, la duración del servicio se administra implícitamente en su nombre. El elemento IServiceProvider y el elemento IHost correspondiente orquestan la limpieza de recursos. En concreto, las implementaciones de IDisposable y IAsyncDisposable se eliminan correctamente al final de su duración especificada.
Para más información, vea Inserción de dependencias en .NET.
Identificadores seguros
Escribir código para el finalizador de un objeto es una tarea compleja que puede causar problemas si no se realiza correctamente. Por lo tanto, recomendamos construir objetos System.Runtime.InteropServices.SafeHandle en lugar de implementar un finalizador.
Un System.Runtime.InteropServices.SafeHandle es un tipo administrado abstracto que encapsula un System.IntPtr que identifica un recurso no administrado. En Windows, podría identificar un identificador y, en Unix, un descriptor de archivo. El SafeHandle
proporciona toda la lógica necesaria para asegurarse de que este recurso se libera una vez y solo una vez, ya sea cuando se elimina el SafeHandle
o cuando se han quitado todas las referencias a la SafeHandle
y se finaliza la instancia de SafeHandle
.
El System.Runtime.InteropServices.SafeHandle es una clase base abstracta. Las clases derivadas proporcionan instancias específicas para diferentes tipos de identificadores. Estas clases derivadas validan qué valores de System.IntPtr se consideran no válidos y cómo liberar realmente el identificador. Por ejemplo, SafeFileHandle se deriva de SafeHandle
para ajustar IntPtrs
que identifican los identificadores o descriptores de archivos abiertos e invalida su método SafeHandle.ReleaseHandle() para cerrarlo (a través de la función close
en Unix o la función CloseHandle
en Windows). La mayoría de las API en las bibliotecas de .NET que crean un recurso no administrado lo encapsulan en un SafeHandle
y, según sea necesario, te devuelven ese SafeHandle
en lugar de entregar el puntero sin procesar. En situaciones en las que interactúe con un componente no administrado y obtenga IntPtr
para un recurso no administrado, puede crear su propio tipo de SafeHandle
para ajustarlo. Como resultado, algunos tipos noSafeHandle
necesitan implementar finalizadores. La mayoría de las implementaciones de patrón descartable solo terminan con el ajuste de otros recursos administrados, algunos de los cuales pueden ser objetos SafeHandle
.
Las siguientes clases derivadas del espacio de nombres Microsoft.Win32.SafeHandles proporcionan identificadores seguros.
Clase | Recursos que contiene |
---|---|
SafeFileHandle SafeMemoryMappedFileHandle SafePipeHandle |
Archivos, archivos mapeados en memoria y canalizaciones |
SafeMemoryMappedViewHandle | Vistas de memoria |
SafeNCryptKeyHandle SafeNCryptProviderHandle SafeNCryptSecretHandle |
Construcciones de criptografía |
SafeRegistryHandle | Claves del Registro |
SafeWaitHandle | Identificadores de espera |
Dispose() y Dispose(bool)
La interfaz IDisposable requiere la implementación de un único método sin parámetros, Dispose. Además, cualquier clase no sellada debe tener un método de sobrecarga Dispose(bool)
.
Las firmas de método son:
-
public
no virtual (NotOverridable
en Visual Basic) (IDisposable.Dispose implementación). protected virtual
(Overridable
en Visual Basic)Dispose(bool)
.
El método Dispose()
Dado que un consumidor del tipo llama a este métodoDispose
public
, no virtual (NotOverridable
en Visual Basic) y sin parámetros cuando ya no se necesita en liberar recursos no administrados, realizar limpiezas generales e indicar que el finalizador, si existe, no tiene que ejecutarse. La liberación de la memoria real asociada a un objeto administrado es siempre una tarea que corresponde al recolector de elementos no utilizados. Por este motivo, tiene una implementación estándar:
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
El método Dispose
limpia todos los objetos, por lo que el recolector de elementos no utilizados no necesita llamar a la invalidación Object.Finalize de los objetos. Por lo tanto, la llamada al método SuppressFinalize impide que el recolector de basura ejecute el finalizador. Si el tipo no tiene ningún finalizador, la llamada a GC.SuppressFinalize no tiene ningún efecto. La limpieza real se realiza mediante la sobrecarga del método Dispose(bool)
.
Sobrecarga del método Dispose(bool)
En la sobrecarga, el parámetro disposing
es un Boolean que indica si la llamada al método procede de un método Dispose (su valor es true
) o de un finalizador (su valor es 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
El parámetro disposing
debe ser false
cuando se llama desde un finalizador y true
cuando se llama desde el método IDisposable.Dispose. En otras palabras, se true
cuando se llama de forma determinista y false
cuando se llama de forma no determinista.
El cuerpo del método consta de tres bloques de código:
Bloque para la devolución condicional si el objeto ya se ha eliminado.
Bloque condicional que libera recursos administrados. Este bloque se ejecuta si el valor de
disposing
estrue
. Los recursos administrados que libera pueden incluir:- Objetos administrados que implementan IDisposable. El bloque condicional se puede utilizar para llamar a la implementación Dispose (eliminación en cascada). Si ha utilizado una clase derivada de System.Runtime.InteropServices.SafeHandle para ajustar el recurso no administrado, debe llamar aquí a la implementación SafeHandle.Dispose().
- Objetos administrados que consumen grandes cantidades de memoria o consumen recursos escasos. Asigne referencias de objetos administrados de gran tamaño a
null
para que sean más propensos a ser inaccesibles. De este modo, se liberan más rápido que si se recuperaran de forma no determinista.
Bloque que libera recursos no administrados. Este bloque se ejecuta independientemente del valor del parámetro
disposing
.
Si la llamada al método procede de un finalizador, solo se debe ejecutar el código que libera recursos no administrados. El implementador es responsable de garantizar que la ruta de acceso falsa no interactúe con objetos administrados que se hayan eliminado. Esto es importante porque el orden en el que el recolector de basura libera los objetos administrados durante la finalización no es determinista.
Llamadas de eliminación en cascada
Si su clase posee un campo o propiedad y su tipo implementa IDisposable, la propia clase contenedora también debe implementar IDisposable. Una clase que crea instancias de una implementación de IDisposable y la almacena como un miembro de instancia, también es responsable de su limpieza. Esto ayuda a garantizar que los tipos descartables a los que se hace referencia tienen la oportunidad de realizar una limpieza determinista a través del método Dispose. En el ejemplo siguiente, la clase es sealed
(o NotInheritable
en 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
Sugerencia
- Si la clase tiene un campo o una propiedad IDisposable, pero no los posee (es decir, la clase no crea el objeto), la clase no necesita implementar IDisposable.
- En algunos casos, es posible que quiera realizar una comprobación de valores
null
en un finalizador (que incluye el métodoDispose(false)
invocado por un finalizador). Una de las razones principales es si no está seguro de si la instancia se ha inicializado completamente (por ejemplo, se podría producir una excepción en un constructor).
Implementación del patrón Dispose
Todas las clases no selladas (o las clases de Visual Basic no modificadas como NotInheritable
) deben considerarse una clase base potencial, ya que podrían heredarse. Cuando se implementa el patrón de Dispose para cualquier clase base potencial, debe proporcionar lo siguiente:
- Implementación de Dispose que llama al método
Dispose(bool)
. - Método
Dispose(bool)
que realiza la limpieza propiamente dicha. - Una clase derivada de SafeHandle que encapsula su recurso no administrado (recomendado), o una sobrescritura del método Object.Finalize. La clase SafeHandle proporciona un finalizador, por lo que no tiene que escribir uno usted mismo.
Importante
Es posible que una clase base solo haga referencia a objetos administrados e implemente el patrón dispose. En estos casos, un finalizador no es necesario. Solo se requiere un finalizador si hace referencia directamente a recursos no administrados.
A continuación se muestra un ejemplo general para implementar el patrón de Dispose para una clase base que utiliza un controlador 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
En el ejemplo anterior se usa un objeto SafeFileHandle para ilustrar el patrón; En su lugar, se podría usar cualquier objeto derivado de SafeHandle. Tenga en cuenta que el ejemplo no crea correctamente una instancia de su objeto SafeFileHandle.
Este es el patrón general para implementar el patrón dispose para una clase base que invalida 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
Sugerencia
En C#, se implementa una finalización proporcionando un finalizador, no invalidando Object.Finalize. En Visual Basic, creas un finalizador con Protected Overrides Sub Finalize()
.
Implementación del patrón dispose para una clase derivada
Una clase derivada de una clase que implementa la interfaz IDisposable no debe implementar IDisposable, porque la implementación de IDisposable.Dispose de la clase base es heredada por sus clases derivadas. En su lugar, para limpiar una clase derivada, debe proporcionar los siguientes elementos:
- Método
protected override void Dispose(bool)
que invalida el método de clase base y realiza la limpieza real de la clase derivada. Este método también debe llamar albase.Dispose(bool)
método (MyBase.Dispose(bool)
en Visual Basic) pasando el estado de eliminación (bool disposing
parámetro) como argumento. - Una clase derivada de SafeHandle que encapsula tu recurso no administrado (recomendado), o una sobrescritura del método Object.Finalize. La clase SafeHandle proporciona un finalizador que le libera de tener que codificar uno. Si proporciona un finalizador, debe llamar a la sobrecarga de
Dispose(bool)
con un argumentofalse
.
A continuación hay un ejemplo del patrón general para implementar el patrón de Dispose para una clase derivado que utiliza un controlador 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
En el ejemplo anterior se usa un objeto SafeFileHandle para ilustrar el patrón; En su lugar, se podría usar cualquier objeto derivado de SafeHandle. Tenga en cuenta que el ejemplo no crea correctamente una instancia de su objeto SafeFileHandle.
Este es el patrón general para implementar el patrón dispose para una clase derivada que invalida 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