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 que se ha asignado, quitar un elemento que se ha agregado a una colección o señalar la liberación de un bloqueo adquirido.
El recolector de elementos no utilizados de .NET no asigna ni libera memoria no administrada. El modelo para desechar un objeto, lo que se conoce como patrón de Dispose, sirve para imponer orden sobre la duración de un objeto. El patrón de eliminación se usa con los 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 siguientes invocaciones de Dispose no deben 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. Usar GC.KeepAlive tiene sentido para hacer que el objeto no sea válido para la recolección de elementos no utilizados desde el principio de la rutina actual y hasta el momento en que se llamó 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
La escritura de código para el finalizador de un objeto es una tarea compleja que puede producir problemas si no se realiza correctamente. Por tanto, se recomienda construir objetos System.Runtime.InteropServices.SafeHandle en lugar de implementar un finalizador.
Un System.Runtime.InteropServices.SafeHandle es un tipo administrado abstracto que contiene un System.IntPtr que identifica un recurso no administrado. En Windows, puede identificar un identificador y, en UNIX, un descriptor de archivo. SafeHandle
proporciona toda la lógica necesaria para asegurarse de que este recurso se libera una vez y solo una vez, cuando se elimina SafeHandle
o cuando se quitan todas las referencias a SafeHandle
y se finaliza la instancia de SafeHandle
.
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 de las bibliotecas de .NET que crean un recurso no administrado lo encapsularán en SafeHandle
y devolverán ese SafeHandle
según sea necesario, en lugar de volver a entregar el puntero básico. 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 clases derivadas siguientes en el espacio de nombres Microsoft.Win32.SafeHandles proporcionan controladores seguros.
Clase | Recursos que contiene |
---|---|
SafeFileHandle SafeMemoryMappedFileHandle SafePipeHandle |
Archivos, archivos asignados 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 (booleano)
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)
.
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. Debido a esto, se realiza 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 consiguiente, la llamada al método SuppressFinalize evita que el recolector de elementos no utilizados 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 valor 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)
{
// 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
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, es 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.
Un bloque que libera los recursos no administrados. Este bloque se ejecuta independientemente del valor del parámetro
disposing
.Un bloque condicional que libera los recursos administrados. Este bloque se ejecuta si el valor de
disposing
estrue
. Estos son algunos de los recursos administrados que se liberan: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 gran cantidad de memoria o recursos insuficientes. Asigne referencias de objetos administrados grandes a
null
para aumentar la probabilidad de que no se pueda acceder a ellos. De este modo, se liberan más rápido que si se recuperaran de forma no determinista.
Si la llamada al método procede de un finalizador, solo se debe ejecutar el código que libera los recursos no administrados. El implementador es responsable de garantizar que la ruta de acceso falsa no interactúe con los objetos administrados que se pueden haber dispuesto. Esto es importante porque el orden en el que el recolector de elementos no utilizados dispone los objetos administrados durante la finalización no es determinista.
Llamadas de eliminación en cascada
Si la clase posee un campo o una 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 mediante el método Dispose. En el siguiente ejemplo, 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). Principalmente si no sabe con seguridad si la instancia se ha inicializado completamente (por ejemplo, se puede producir una excepción en un constructor).
Implementación del patrón Dispose
Todas las clases no selladas o (clases de Visual Basic no modificadas como NotInheritable
) deben considerarse una clase base potencial, ya que se podrían heredar. Cuando se implementa el patrón de Dispose para cualquier clase base potencial, debe proporcionar lo siguiente:
- Una implementación Dispose que llame al método
Dispose(bool)
. - Un método
Dispose(bool)
que realiza la tarea real de limpieza. - Una clase derivada de SafeHandle que contiene el recurso no administrado (recomendado), o una invalidación del método Object.Finalize. La clase SafeHandle proporciona un finalizador, por lo que no tiene que escribir uno personalmente.
Importante
Es posible que una clase base solo haga referencia a objetos administrados e implemente el patrón de eliminación. En estos casos, un finalizador no es necesario. Un finalizador solo es necesario si se hace referencia directamente a los 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
El ejemplo anterior utiliza un objeto SafeFileHandle para ilustrar el patrón; cualquier objeto derivado de SafeHandle podría usarse en su lugar. Tenga en cuenta que el ejemplo no crea una instancia de su objeto SafeFileHandle correctamente.
A continuación se muestra el patrón general para implementar el patrón de Dispose para una clase base que invalide a 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, se crea un finalizador con Protected Overrides Sub Finalize()
.
Implementación del patrón de Dispose para una clase derivada
Una clase derivada de una clase que implemente la interfaz IDisposable no debe implementar IDisposable, porque la implementación de la clase base de IDisposable.Dispose la heredan sus clases derivadas. En su lugar, para limpiar una clase derivada, debe proporcionar los siguientes elementos:
- Un método
protected override void Dispose(bool)
que invalide el método de la clase base y realice 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 contiene el recurso no administrado (recomendado), o una invalidación del método Object.Finalize. La clase SafeHandle proporciona un finalizador que evita que tenga 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
El ejemplo anterior utiliza un objeto SafeFileHandle para ilustrar el patrón; cualquier objeto derivado de SafeHandle podría usarse en su lugar. Tenga en cuenta que el ejemplo no crea una instancia de su objeto SafeFileHandle correctamente.
A continuación se muestra el patrón general para implementar el patrón de Dispose para una clase derivada que invalide a 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