Compartir a través de


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étodoDisposepublic, 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 es true. 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étodo Dispose(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 al base.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 argumento false.

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

Consulte también