Implementar un método Dispose

 

El método Dispose se implementa para liberar recursos no administrados que usa la aplicación. El recolector de elementos no utilizados de .NET Framework no asigna ni libera memoria no administrada.

Conoce el patrón para desechar un objeto, como un patrón de dispose, sirve para imponer orden sobre la duración de un objeto. El patrón de Dispose se utiliza solo con los objetos que tienen acceso a recursos no administrados, como identificadores de archivo y de canalización, identificadores de registro, identificadores de espera o punteros a bloques de memoria sin administrar. Esto se debe a que el recolector de elementos no utilizados es muy eficaz a la hora de reclamar objetos administrados no usados, aunque no puede reclamar objetos no administrados.

El patrón de Dispose tiene dos variaciones:

Para asegurarse de que los recursos se limpien siempre correctamente, un método Dispose debe ser invocable varias veces sin que se produzca una excepción.

System_CAPS_ICON_important.jpg Importante

Si es un programador de C++, implemente el Dispose método. En su lugar, siga las instrucciones que aparecen en la sección "Destructores y finalizadores" de Cómo: definir y utilizar clases y Structs (C++ / CLI). A partir de .NET Framework 2.0, el compilador de C++ admite la eliminación determinista de recursos y no permite la implementación directa de la Dispose método.

El ejemplo de código proporcionado para el método GC.KeepAlive muestra cómo la recolección de elementos no utilizados rigurosa puede hacer que se ejecute un finalizador mientras un miembro del objeto reclamado todavía se está ejecutando. Es una buena idea para llamar a la KeepAlive método al final de una larga Dispose método.

La interfaz IDisposable requiere la implementación de un único método sin parámetros, Dispose. Sin embargo, el patrón de Dispose requiere dos métodos Dispose para implementarse:

  • Una implementación pública que no sea virtual (NonInheritable en Visual Basic) IDisposable.Dispose y que no tenga parámetros.

  • Un método protegido virtual (Overridable in Visual Basic) Dispose cuya signatura es:

       protected virtual void Dispose(bool disposing)
    

La sobrecarga Dispose()

Dado que un consumidor del tipo llama a este método NonInheritable público, no virtual (Dispose en Visual Basic) y sin parámetros, su propósito consiste en liberar recursos no administrados e indicar que el finalizador, si existe, no tiene que ejecutarse. Debido a esto, se realiza una implementación estándar:

   public void Dispose()
   {
      // Dispose of unmanaged resources.
      Dispose(true);
      // Suppress finalization.
      GC.SuppressFinalize(this);
   }

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 a la SuppressFinalize método 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. Observe que el trabajo real de liberar recursos no administrados lo realiza la segunda sobrecarga del método Dispose.

La sobrecarga Dispose(Boolean)

En la segunda sobrecarga, el disposing parámetro es un booleano que indica si la llamada al método procede de un Dispose (método) (su valor es true) o de un finalizador (su valor es false).

El cuerpo del método consta de dos bloques de código:

  • 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 es true. Estos son algunos de los recursos administrados que se liberan:

    Objetos administrados que implementan IDisposable.
    El bloque condicional se puede usar para llamar a la implementación Dispose. Si ha utilizado un controlador seguro para incluir el recurso no administrado, debe llamar a la SafeHandle.Dispose(Boolean) aquí la implementación.

    Objetos administrados que consumen gran cantidad de memoria o recursos insuficientes.
    Al liberar estos objetos explícitamente en el método Dispose, se liberan más rápido que si el recolector de elementos no utilizados los reclamara de forma no determinista.

Si la llamada al método procede de un finalizador (es decir, si disposing es false), solo se ejecuta el código que libera los recursos no administrados. Como no se define el orden en que el recolector de elementos no utilizados destruye los objetos administrados durante la finalización, la llamada a esta sobrecarga Dispose con un valor de false evita que el finalizador intente liberar los recursos administrados que ya se hayan reclamado.

Cuando se implementa el patrón de Dispose para una clase base, debe proporcionar lo siguiente:

System_CAPS_ICON_important.jpg Importante

Debe implementar este patrón para todas las clases bases que implementan IDisposable y no sealed (NotInheritable en Visual Basic).

  • Una implementación Dispose que llame al método Dispose(Boolean).

  • Un método Dispose(Boolean) que realiza el trabajo real de liberar recursos.

  • 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 programar uno.

A continuación se muestra el patrón 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;

class BaseClass : IDisposable
{
   // Flag: Has Dispose already been called?
   bool disposed = false;
   // Instantiate a SafeHandle instance.
   SafeHandle handle = 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 (disposed)
         return; 
      
      if (disposing) {
         handle.Dispose();
         // Free any other managed objects here.
         //
      }
      
      // Free any unmanaged objects here.
      //
      disposed = true;
   }
}

System_CAPS_ICON_note.jpg Nota

El ejemplo anterior usa 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;

class BaseClass : IDisposable
{
   // Flag: Has Dispose already been called?
   bool disposed = 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 (disposed)
         return; 
      
      if (disposing) {
         // Free any other managed objects here.
         //
      }
      
      // Free any unmanaged objects here.
      //
      disposed = true;
   }

   ~BaseClass()
   {
      Dispose(false);
   }
}

System_CAPS_ICON_note.jpg Nota

En C#, invalide Object.Finalize definiendo un destructor.

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 implementar el patrón de Dispose para una clase derivada, debe proporcionar lo siguiente:

  • Un método protected``Dispose(Boolean) que invalide el método de la clase base y realice el trabajo real de liberar los recursos de la clase derivada. Este método también debe llamar al método Dispose(Boolean) de la clase base y pasarle un valor true para el argumento disposing.

  • 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 programar uno. Si proporciona un finalizador, debe llamar a la sobrecarga de Dispose(Boolean) con un argumento disposing que sea false.

A continuación se muestra el patrón general para implementar el patrón de Dispose para una clase derivada que utiliza un controlador seguro:

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

class DerivedClass : BaseClass
{
   // Flag: Has Dispose already been called?
   bool disposed = false;
   // Instantiate a SafeHandle instance.
   SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);

   // Protected implementation of Dispose pattern.
   protected override void Dispose(bool disposing)
   {
      if (disposed)
         return; 
      
      if (disposing) {
         handle.Dispose();
         // Free any other managed objects here.
         //
      }
      
      // Free any unmanaged objects here.
      //

      disposed = true;
      // Call base class implementation.
      base.Dispose(disposing);
   }
}

System_CAPS_ICON_note.jpg Nota

El ejemplo anterior usa 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:

using System;

class DerivedClass : BaseClass
{
   // Flag: Has Dispose already been called?
   bool disposed = false;
   
   // Protected implementation of Dispose pattern.
   protected override void Dispose(bool disposing)
   {
      if (disposed)
         return; 
      
      if (disposing) {
         // Free any other managed objects here.
         //
      }
      
      // Free any unmanaged objects here.
      //
      disposed = true;
      
      // Call the base class implementation.
      base.Dispose(disposing);
   }

   ~DerivedClass()
   {
      Dispose(false);
   }
}

System_CAPS_ICON_note.jpg Nota

En C#, invalide Object.Finalize definiendo un destructor.

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.

Las clases derivadas de la clase System.Runtime.InteropServices.SafeHandle simplifican los problemas de duración de objetos mediante la asignación y liberación de identificadores sin interrupción. Contienen un finalizador crítico cuya ejecución está garantizada mientras se descarga un dominio de aplicación. Para obtener más información acerca de las ventajas de usar un controlador seguro, consulte System.Runtime.InteropServices.SafeHandle. Las clases derivadas siguientes en el espacio de nombres Microsoft.Win32.SafeHandles proporcionan controladores seguros:

En el ejemplo siguiente se muestra el patrón de Dispose para una clase base, DisposableStreamResource, que utiliza un controlador seguro para encapsular los recursos no administrados. Define una clase DisposableResource que usa SafeFileHandle para incluir un objeto Stream que representa un archivo abierto. El método DisposableResource también incluye una propiedad única, Size, que devuelve el número total de bytes de la secuencia de archivos.

using Microsoft.Win32.SafeHandles;
using System;
using System.IO;
using System.Runtime.InteropServices;

public class DisposableStreamResource : IDisposable
{
   // Define constants.
   protected const uint GENERIC_READ = 0x80000000;
   protected const uint FILE_SHARE_READ = 0x00000001;
   protected const uint OPEN_EXISTING = 3;
   protected const uint FILE_ATTRIBUTE_NORMAL = 0x80;
   protected IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
   private const int INVALID_FILE_SIZE = unchecked((int) 0xFFFFFFFF);
   
   // Define Windows APIs.
   [DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode)]
   protected static extern IntPtr CreateFile (
                                  string lpFileName, uint dwDesiredAccess, 
                                  uint dwShareMode, IntPtr lpSecurityAttributes, 
                                  uint dwCreationDisposition, uint dwFlagsAndAttributes, 
                                  IntPtr hTemplateFile);
   
   [DllImport("kernel32.dll")]
   private static extern int GetFileSize(SafeFileHandle hFile, out int lpFileSizeHigh);
    
   // Define locals.
   private bool disposed = false;
   private SafeFileHandle safeHandle; 
   private long bufferSize;
   private int upperWord;
   
   public DisposableStreamResource(string filename)
   {
      if (filename == null)
         throw new ArgumentNullException("The filename cannot be null.");
      else if (filename == "")
         throw new ArgumentException("The filename cannot be an empty string.");
            
      IntPtr handle = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ,
                                 IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
                                 IntPtr.Zero);
      if (handle != INVALID_HANDLE_VALUE)
         safeHandle = new SafeFileHandle(handle, true);
      else
         throw new FileNotFoundException(String.Format("Cannot open '{0}'", filename));
      
      // Get file size.
      bufferSize = GetFileSize(safeHandle, out upperWord); 
      if (bufferSize == INVALID_FILE_SIZE)
         bufferSize = -1;
      else if (upperWord > 0) 
         bufferSize = (((long)upperWord) << 32) + bufferSize;
   }
   
   public long Size 
   { get { return bufferSize; } }

   public void Dispose()
   {
      Dispose(true);
      GC.SuppressFinalize(this);
   }           

   protected virtual void Dispose(bool disposing)
   {
      if (disposed) return;

      // Dispose of managed resources here.
      if (disposing)
         safeHandle.Dispose();
      
      // Dispose of any unmanaged resources not wrapped in safe handles.
      
      disposed = true;
   }  
}

En el ejemplo siguiente se muestra el patrón de Dispose para una clase derivada, DisposableStreamResource2, que se hereda de la clase DisposableStreamResource mostrada en el ejemplo anterior. La clase agrega un método adicional, WriteFileInfo, y usa un objeto SafeFileHandle para incluir el identificador del archivo editable.

using Microsoft.Win32.SafeHandles;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

public class DisposableStreamResource2 : DisposableStreamResource
{
   // Define additional constants.
   protected const uint GENERIC_WRITE = 0x40000000; 
   protected const uint OPEN_ALWAYS = 4;
   
   // Define additional APIs.
   [DllImport("kernel32.dll")]   
   protected static extern bool WriteFile(
                                SafeFileHandle safeHandle, string lpBuffer, 
                                int nNumberOfBytesToWrite, out int lpNumberOfBytesWritten,
                                IntPtr lpOverlapped);
   
   // Define locals.
   private bool disposed = false;
   private string filename;
   private bool created = false;
   private SafeFileHandle safeHandle;
   
   public DisposableStreamResource2(string filename) : base(filename)
   {
      this.filename = filename;
   }
   
   public void WriteFileInfo()
   { 
      if (! created) {
         IntPtr hFile = CreateFile(@".\FileInfo.txt", GENERIC_WRITE, 0, 
                                   IntPtr.Zero, OPEN_ALWAYS, 
                                   FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
         if (hFile != INVALID_HANDLE_VALUE)
            safeHandle = new SafeFileHandle(hFile, true);
         else
            throw new IOException("Unable to create output file.");

         created = true;
      }

      string output = String.Format("{0}: {1:N0} bytes\n", filename, Size);
      int bytesWritten;
      bool result = WriteFile(safeHandle, output, output.Length, out bytesWritten, IntPtr.Zero);                                     
   }

   protected new virtual void Dispose(bool disposing)
   {
      if (disposed) return;
      
      // Release any managed resources here.
      if (disposing)
         safeHandle.Dispose();
      
      disposed = true;
      
      // Release any unmanaged resources not wrapped by safe handles here.
      
      // Call the base class implementation.
      base.Dispose(true);
   }
}

SuppressFinalize
IDisposable
IDisposable.Dispose
Microsoft.Win32.SafeHandles
System.Runtime.InteropServices.SafeHandle
Object.Finalize
Cómo: definir y utilizar clases y Structs (C++ / CLI)
Patrón de Dispose

Mostrar: