Implementar un método Dispose

El método Dispose de un tipo deber liberar todos los recursos de su propiedad. También debe liberar todos los recursos que posean sus tipos base; para ello, llama al método Dispose de su tipo primario. El método Dispose del tipo primario debe liberar todos los recursos que posee y, a su vez, llamar al método Dispose de su tipo primario, propagando este patrón por la jerarquía de tipos base. Para asegurarse de que los recursos se limpien siempre correctamente, se debe poder llamar a un método Dispose varias veces sin que provoque una excepción.

NoteImportante

Los programadores de C++ no deben utilizar este tema. En su lugar, vea Destructors and Finalizers in Visual C++. En .NET Framework versión 2.0, el compilador de C++ proporciona compatibilidad para implementar la eliminación determinística de los recursos y no permite la implementación directa del método Dispose.

Un método Dispose debe llamar al método GC.SuppressFinalize del objeto que está desechando. Si el objeto se encuentra en la cola de finalización en ese momento, GC.SuppressFinalize evita que se llame a su método Finalize. Recuerde que ejecutar un método Finalize afecta al rendimiento. Si el método Dispose ya ha limpiado el objeto, no es necesario que el recolector de elementos no utilizados llame al método Finalize del objeto.

Nota

El ejemplo de código proporcionado para el método System.GC.KeepAlive(System.Object) 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. Suele ser recomendable llamar al método KeepAlive al final de un método Dispose prolongado.

La finalidad del siguiente ejemplo de código es mostrar el modelo de diseño recomendado para implementar un método Dispose para las clases que encapsulan recursos no administrados. Este modelo se implementa en todo .NET Framework.

Las clases de recursos se derivan normalmente de clases nativas complejas o de las API y se deben personalizar de manera conveniente. Utilice este patrón de código como punto de partida para la creación de una clase de recursos y proporcione la personalización necesaria de acuerdo con los recursos que se encapsulan. No se puede compilar este ejemplo para utilizarlo directamente en una aplicación.

En este ejemplo, la clase base BaseResource implementa un método Dispose público que pueden llamar los usuarios de la clase. A su vez, llama al método virtual Dispose(bool disposing) (virtual Dispose(disposing As Boolean) en Visual Basic). Se pasan true o false, dependiendo de la identidad del llamador. El código de limpieza correspondiente al objeto se ejecuta en el método Dispose virtual.

Dispose(bool disposing) se ejecuta en dos escenarios distintos. Si disposing es igual a true, un código de usuario ha llamado directa o indirectamente al método y se pueden desechar los recursos administrados y no administrados. Si disposing es false, el motor en tiempo de ejecución ha llamado al método desde el interior del finalizador y sólo se pueden desechar los recursos no administrados. Cuando un objeto ejecuta el código de finalización, no debería hacer referencia a otros objetos porque los finalizadores no se ejecutan en ningún orden especial. Si un finalizador en ejecución hace referencia a otro objeto que ya ha finalizado, el finalizador en ejecución fallará.

La clase base dispone de un destructor o método Finalize como medida de seguridad en caso de que no se llame al método Dispose. El método Finalize llama al método Dispose que acepta parámetros, pasando false. No debería volver a crear un código de limpieza Dispose en el método Finalize. La llamada de Dispose(false) es óptima para la legibilidad y mantenimiento del código.

La clase MyResourceWrapper ilustra cómo derivar de una clase que implementa la administración de recursos utilizando Dispose. MyResourceWrapper reemplaza el método virtual Dispose(bool disposing) y proporciona un código de limpieza para los recursos administrados y no administrados que crea. MyResourceWrapper llama también a Dispose en su clase base BaseResource para asegurarse de que su base puede limpiarse correctamente. Tenga en cuenta que la clase derivada MyResourceWrapper no tiene el método Finalize ni un método Dispose sin parámetros, ya que los hereda de la clase base BaseResource.

Nota

El método protected Dispose(bool disposing) de este ejemplo no impone la seguridad de los subprocesos porque no se puede llamar a este método desde un subproceso de usuario y un subproceso finalizador al mismo tiempo. Además, una aplicación cliente que utilice BaseResource no debería permitir nunca que subprocesos de usuario múltiples llamen al método protected Dispose(bool disposing) al mismo tiempo. Una aplicación o biblioteca de clases debería estar diseñada de forma que permitiera que sólo un subproceso fuera el propietario del período de duración de un recurso y llamara a Dispose cuando el recurso ya no fuera necesario. Dependiendo del recurso, el acceso a un subproceso sin sincronizar al desechar recursos puede suponer un riesgo de seguridad. Los programadores deberían revisar cuidadosamente el código para determinar cuál es el mejor enfoque para imponer la seguridad del subproceso.

' Design pattern for the base class.
' By implementing IDisposable, you are announcing that instances
' of this type allocate scarce resources.
Public Class BaseResource
   Implements IDisposable
   ' Pointer to an external unmanaged resource.
   Private handle As IntPtr 
   ' Other managed resource this class uses.
   Private Components As Component
   ' Track whether Dispose has been called.
   Private disposed As Boolean = False

   ' Constructor for the BaseResource Object.
   Public Sub New()
      ' Insert appropriate constructor code here.
   End Sub

   ' Implement IDisposable.
   ' Do not make this method Overridable.
   ' A derived class should not be able to override this method.
   Public Overloads Sub Dispose()Implements IDisposable.Dispose
      Dispose(true)
      ' Take yourself off of the finalization queue
      ' to prevent finalization code for this object
      ' from executing a second time.
      GC.SuppressFinalize(Me) 
   End Sub

' Dispose(disposing As Boolean) executes in two distinct scenarios.
' If disposing is true, the method has been called directly 
' or indirectly by a user's code. Managed and unmanaged resources 
' can be disposed.
' If disposing equals false, the method has been called by the runtime
' from inside the finalizer and you should not reference other    
' objects. Only unmanaged resources can be disposed.
Protected Overloads Overridable Sub Dispose(disposing As Boolean)
   ' Check to see if Dispose has already been called.
   If Not (Me.disposed) Then
      ' If disposing equals true, dispose all managed 
      ' and unmanaged resources.
      If (disposing) Then
         ' Dispose managed resources.
         Components.Dispose()
      End If
      ' Release unmanaged resources. If disposing is false,
      ' only the following code is executed.      
      CloseHandle(handle)
      handle = IntPtr.Zero
      ' Note that this is not thread safe.
      ' Another thread could start disposing the object
      ' after the managed resources are disposed,
      ' but before the disposed flag is set to true.
      ' If thread safety is necessary, it must be
      ' implemented by the client.
   End If
   Me.disposed = true
End Sub

   ' This Finalize method will run only if the 
   ' Dispose method does not get called.
   ' By default, methods are NotOverridable. 
   ' This prevents a derived class from overriding this method.
   Protected Overrides Sub Finalize()
         ' Do not re-create Dispose clean-up code here.
         ' Calling Dispose(false) is optimal in terms of
         ' readability and maintainability.
         Dispose(false)
   End Sub
   
   ' Allow your Dispose method to be called multiple times,
   ' but throw an exception if the object has been disposed.
   ' Whenever you do something with this class, 
   ' check to see if it has been disposed.
   Public Sub DoSomething()
      If Me.disposed Then
         Throw New ObjectDisposedException()
      End if
   End Sub
End Class

' Design pattern for a derived class.
' Note that this derived class inherently implements the 
' IDisposable interface because it is implemented in the base class.
Public Class MyResourceWrapper
   Inherits BaseResource
   
   ' A managed resource that you add in this derived class.
   private addedManaged As ManagedResource
   ' A native unmanaged resource that you add in this derived class.
   private addedNative As NativeResource
   ' Track whether Dispose has been called.
   Private disposed As Boolean = False

   ' Constructor for the MyResourceWrapper object.
   Public Sub New()      
      MyBase.New()
      ' Insert appropriate constructor code here for the
      ' added resources.
   End Sub

   Protected Overloads Overrides Sub Dispose(disposing As Boolean)
      If Not (Me.disposed) Then
         Try
            If disposing Then
              ' Release the managed resources you added in
              ' this derived class here.
              addedManaged.Dispose()
            End If
            ' Release the native unmanaged resources you added
            ' in this derived class here.
            CloseHandle(addedNative)
            Me.disposed = true
         Finally
            ' Call Dispose on your base class.
            MyBase.Dispose(disposing)
         End Try
      End If
   End Sub
End Class
' This derived class does not have a Finalize method
' or a Dispose method without parameters because it 
' inherits them from the base class.
// Design pattern for the base class.
// By implementing IDisposable, you are announcing that instances
// of this type allocate scarce resources.
public class BaseResource: IDisposable
{
   // Pointer to an external unmanaged resource.
   private IntPtr handle;
   // Other managed resource this class uses.
   private Component Components;
   // Track whether Dispose has been called.
   private bool disposed = false;

   // Constructor for the BaseResource object.
   public BaseResource()
   {
      // Insert appropriate constructor code here.
   }

   // Implement IDisposable.
   // Do not make this method virtual.
   // A derived class should not be able to override this method.
   public void Dispose()
   {
      Dispose(true);
      // Take yourself off the Finalization queue 
      // to prevent finalization code for this object
      // from executing a second time.
      GC.SuppressFinalize(this);
   }

   // Dispose(bool disposing) executes in two distinct scenarios.
   // If disposing equals true, the method has been called directly
   // or indirectly by a user's code. Managed and unmanaged resources
   // can be disposed.
   // If disposing equals false, the method has been called by the 
   // runtime from inside the finalizer and you should not reference 
   // other objects. Only unmanaged resources can be disposed.
   protected virtual void Dispose(bool disposing)
   {
      // Check to see if Dispose has already been called.
      if(!this.disposed)
      {
         // If disposing equals true, dispose all managed 
         // and unmanaged resources.
         if(disposing)
         {
            // Dispose managed resources.
            Components.Dispose();
         }
         // Release unmanaged resources. If disposing is false, 
         // only the following code is executed.
         CloseHandle(handle);
         handle = IntPtr.Zero;
         // Note that this is not thread safe.
         // Another thread could start disposing the object
         // after the managed resources are disposed,
         // but before the disposed flag is set to true.
         // If thread safety is necessary, it must be
         // implemented by the client.

      }
      disposed = true;         
   }

   // Use C# destructor syntax for finalization code.
   // This destructor will run only if the Dispose method 
   // does not get called.
   // It gives your base class the opportunity to finalize.
   // Do not provide destructors in types derived from this class.
   ~BaseResource()      
   {
      // Do not re-create Dispose clean-up code here.
      // Calling Dispose(false) is optimal in terms of
      // readability and maintainability.
      Dispose(false);
   }

   // Allow your Dispose method to be called multiple times,
   // but throw an exception if the object has been disposed.
   // Whenever you do something with this class, 
   // check to see if it has been disposed.
   public void DoSomething()
   {
      if(this.disposed)
      {
         throw new ObjectDisposedException();
      }
   }
}

// Design pattern for a derived class.
// Note that this derived class inherently implements the 
// IDisposable interface because it is implemented in the base class.
public class MyResourceWrapper: BaseResource
{
   // A managed resource that you add in this derived class.
   private ManagedResource addedManaged;
   // A native unmanaged resource that you add in this derived class.
   private NativeResource addedNative;
   private bool disposed = false;

  // Constructor for this object.
   public MyResourceWrapper()
   {
      // Insert appropriate constructor code here.
   }

   protected override void Dispose(bool disposing)
   {
      if(!this.disposed)
      {
         try
         {
            if(disposing)
            {
               // Release the managed resources you added in
               // this derived class here.
               addedManaged.Dispose();         
            }
            // Release the native unmanaged resources you added
            // in this derived class here.
            CloseHandle(addedNative);
            this.disposed = true;
         }
         finally
         {
            // Call Dispose on your base class.
            base.Dispose(disposing);
         }
      }
   }
}

// This derived class does not have a Finalize method
// or a Dispose method without parameters because it inherits 
// them from the base class.

Implementar un método Close

Agregue un método Close público al tipo base para los que llamar a un método Close resulte más natural que llamar a un método Dispose. A su vez, el método Close llama al método Dispose sin parámetros, el cual realiza las operaciones de limpieza apropiadas. En el siguiente ejemplo de código se muestra un método Close.

' Do not make this method Overridable.
' A derived class should not be allowed
' to override this method.
Public Sub Close()
   ' Calls the Dispose method without parameters.
   Dispose()
End Sub
// Do not make this method virtual.
// A derived class should not be allowed
// to override this method.
public void Close()
{
   // Calls the Dispose method without parameters.
   Dispose();
}

Vea también

Referencia

GC.SuppressFinalize Method
Destructors and Finalizers in Visual C++
Implementar Finalize y Dispose para limpiar recursos no administrados

Conceptos

Reemplazar el método Finalize