Implementar Finalize y Dispose para limpiar recursos no administrados

NotaNota

Para obtener información sobre concluir y disponibilidad de los recursos mediante C++, vea Destructors and Finalizers in Visual C++.

A menudo, las instancias de clases encapsulan el control sobre los recursos que no administra el motor en tiempo de ejecución, como identificadores de ventanas (HWND), conexiones a bases de datos, etc. Por consiguiente, deberá proporcionar las formas explícita e implícita de liberar esos recursos. Proporcione el control implícito implementando el método protegido Finalize en un objeto (sintaxis de destructor de C# y de C++). El recolector de elementos no utilizados llama a estos métodos en algún momento, cuando ya no hay ninguna referencia válida al objeto.

En algunos casos, se puede proporcionar a los programadores la utilización de un objeto con capacidad para liberar explícitamente estos recursos externos, antes de que el recolector de elementos no utilizados libere el objeto. Si el recurso externo es insuficiente o resulta muy caro, se puede conseguir un mejor rendimiento si el programador libera explícitamente los recursos cuando ya no se utilizan. Para proporcionar control explícito, implemente el método Dispose proporcionado por IDisposable. El consumidor del objeto debería llamar a este método cuando haya terminado de utilizarlo. Se puede llamar al método Dispose incluso aunque todavía existan referencias activas al objeto.

Hay que tener en cuenta que cuando se proporciona el control explícito mediante el método Dispose, se debe proporcionar la limpieza implícita utilizando el método Finalize. Finalize proporciona una copia de seguridad para evitar que los recursos se pierdan permanentemente si el programador no llama al método Dispose.

Para obtener más información sobre cómo implementar Finalize y Dispose para limpiar los recursos no administrados, vea recolección de elementos no utilizados. En el siguiente ejemplo se muestra el modelo de diseño básico para implementar *** Dispose ***. Este ejemplo requiere el espacio de nombres System.

' Design pattern for a base class.

Public Class Base
   Implements IDisposable
    ' Field to handle multiple calls to Dispose gracefully.
    Dim disposed as Boolean = false

   ' Implement IDisposable.
   Public Overloads Sub Dispose() Implements IDisposable.Dispose
      Dispose(True)
      GC.SuppressFinalize(Me)
   End Sub

   Protected Overloads Overridable Sub Dispose(disposing As Boolean)
      If disposed = False Then
          If disposing Then
             ' Free other state (managed objects).
             disposed = True
          End If
          ' Free your own state (unmanaged objects).
          ' Set large fields to null.
      End If
   End Sub

   Protected Overrides Sub Finalize()
      ' Simply call Dispose(False).
      Dispose (False)
   End Sub
End Class

' Design pattern for a derived class.
Public Class Derived
   Inherits Base

    ' Field to handle multiple calls to Dispose gracefully.
    Dim disposed as Boolean = false

   Protected Overloads Overrides Sub Dispose(disposing As Boolean)
      If disposed = False Then
          If disposing Then
             ' Release managed resources.
          End If
          ' Release unmanaged resources.
          ' Set large fields to null.
          disposed = True
      End If
      ' Call Dispose on your base class.
      Mybase.Dispose(disposing)
   End Sub
   ' The derived class does not have a Finalize method
   ' or a Dispose method without parameters because it inherits
   ' them from the base class.
End Class
// Design pattern for a base class.
public class Base: IDisposable
{
    private bool disposed = false;

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

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Free other state (managed objects).
            }
            // Free your own state (unmanaged objects).
            // Set large fields to null.
            disposed = true;
        }
    }

    // Use C# destructor syntax for finalization code.
    ~Base()
    {
        // Simply call Dispose(false).
        Dispose (false);
    }
}

// Design pattern for a derived class.
public class Derived: Base
{
    private bool disposed = false;

    protected override void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Release managed resources.
            }
            // Release unmanaged resources.
            // Set large fields to null.
           // Call Dispose on your base class.
            disposed = true;
        }
        base.Dispose(disposing);
    }
    // The derived class does not have a Finalize method
    // or a Dispose method without parameters because it inherits
    // them from the base class.
}

El código siguiente se expande el ejemplo anterior a mostrar las diferentes maneras *** Dispose *** se invocó y, cuando se llama a *** Finalize ***. Las fases del modelo de eliminación se siguen con el resultado en la consola. La asignación y liberación de un recurso no administrado se controla en la clase derivada.

Imports System
Imports System.Collections.Generic
Imports System.Runtime.InteropServices

' Design pattern for a base class.
Public MustInherit Class Base
    Implements IDisposable

    Private disposed as Boolean = false
    Private instName As String
    Private trackingList As List(Of Object)

    Public Sub New(instanceName As String, tracking As List(Of Object))
        MyClass.instName = instanceName
        trackingList = tracking
        trackingList.Add(Me)
    End Sub

    Public ReadOnly Property InstanceName() As String
        Get
            Return instName
        End Get
    End Property

    'Implement IDisposable.
    Public Overloads Sub Dispose() Implements IDisposable.Dispose
        Console.WriteLine(vbNewLine + "[{0}].Base.Dispose()", instName)
        Dispose(true)
        GC.SuppressFinalize(Me)
    End Sub

    Protected Overloads Overridable Sub Dispose(disposing As Boolean)
        If disposed = False Then
            If disposing Then
                ' Free other state (managed objects).
                Console.WriteLine("[{0}].Base.Dispose(true)", instName)
                trackingList.Remove(Me)
                Console.WriteLine("[{0}] Removed from tracking list: {1:x16}",
                    instanceName, MyClass.GetHashCode())
            Else
                Console.WriteLine("[{0}].Base.Dispose(false)", instName)
            End If
            disposed = True
        End If
    End Sub

    Protected Overrides Sub Finalize()
        ' Simply call Dispose(False).
        Console.WriteLine(vbNewLine + "[{0}].Base.Finalize()", instName)
        Dispose(False)
    End Sub
End Class

' Design pattern for a derived class.
Public Class Derived
   Inherits Base

    Private disposed as Boolean = false
    Private umResource As IntPtr

    Public Sub New(instanceName As String, tracking As List(Of Object))
        MyBase.New(instanceName, tracking)
        ' Save the instance name as an unmanaged resource
        umResource = Marshal.StringToCoTaskMemAuto(instanceName)
    End Sub

    Protected Overloads Overrides Sub Dispose(disposing As Boolean)
        If disposed = False Then
            If disposing Then
                Console.WriteLine("[{0}].Derived.Dispose(true)", InstanceName)
                ' Release managed resources.
            Else
                Console.WriteLine("[{0}].Derived.Dispose(false)", InstanceName)
            End If
           ' Release unmanaged resources.
            If umResource <> IntPtr.Zero
                Marshal.FreeCoTaskMem(umResource)
                Console.WriteLine("[{0}] Unmanaged memory freed at {1:x16}", _
                    InstanceName, umResource.ToInt64())
                umResource = IntPtr.Zero
            End If
            disposed = True
        End If
        ' Call Dispose in the base class.
        MyBase.Dispose(disposing)
    End Sub
    ' The derived class does not have a Finalize method
    ' or a Dispose method without parameters because it inherits
    ' them from the base class.
End Class

Public Class TestDisposal
    Public Shared Sub Main()
        Dim tracking As New List(Of Object)()

        ' Dispose is not called, Finalize will be called later.
        Using Nothing
            Console.WriteLine(vbNewLine + "Disposal Scenario: #1" + vbNewLine)
            Dim d3 As New Derived("d1", tracking)
        End Using

        ' Dispose is implicitly called in the scope of the using statement.
        Using d1 As New Derived("d2", tracking)
            Console.WriteLine(vbNewLine + "Disposal Scenario: #2" + vbNewLine)
        End Using

        ' Dispose is explicitly called.
        Using Nothing
            Console.WriteLine(vbNewLine + "Disposal Scenario: #3" + vbNewLine)
            Dim d2 As New Derived("d3", tracking)
            d2.Dispose()
        End Using

        ' Again, Dispose is not called, Finalize will be called later.
        Using Nothing
            Console.WriteLine(vbNewLine + "Disposal Scenario: #4" + vbNewLine)
            Dim d4 As New Derived("d4", tracking)
        End Using

        ' List the objects remaining to dispose.
        Console.WriteLine(vbNewLine + "Objects remaining to dispose = {0:d}", tracking.Count)
        For Each dd As Derived in tracking
            Console.WriteLine("    Reference Object: {0:s}, {1:x16}",
                dd.InstanceName, dd.GetHashCode())
        Next dd
        ' Queued finalizers will be exeucted when Main() goes out of scope.
        Console.WriteLine(vbNewLine + "Dequeueing finalizers...")
    End Sub
End Class

' The program will display output similar to the following:
'
' Disposal Scenario: #1
'
'
' Disposal Scenario: #2
'
'
' [d2].Base.Dispose()
' [d2].Derived.Dispose(true)
' [d2] Unmanaged memory freed at 00000000001ce420
' [d2].Base.Dispose(true)
' [d2] Removed from tracking list: 0000000002bf8098
'
' Disposal Scenario: #3
'
'
' [d3].Base.Dispose()
' [d3].Derived.Dispose(true)
' [d3] Unmanaged memory freed at 00000000001ce420
' [d3].Base.Dispose(true)
' [d3] Removed from tracking list: 0000000000bb8560
'
' Disposal Scenario: #4
'
'
' Objects remaining to dispose = 2
'     Reference Object: d1, 000000000297b065
'     Reference Object: d4, 0000000003553390
'
' Dequeueing finalizers...
'
' [d4].Base.Finalize()
' [d4].Derived.Dispose(false)
' [d4] Unmanaged memory freed at 00000000001ce420
' [d4].Base.Dispose(false)
'
' [d1].Base.Finalize()
' [d1].Derived.Dispose(false)
' [d1] Unmanaged memory freed at 00000000001ce3f0
' [d1].Base.Dispose(false)
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

// Design pattern for a base class.
public abstract class Base : IDisposable
{
    private bool disposed = false;
    private string instanceName;
    private List<object> trackingList;

    public Base(string instanceName, List<object> tracking)
    {
        this.instanceName = instanceName;
         trackingList = tracking;
         trackingList.Add(this);
    }

    public string InstanceName
    {
        get
        {
            return instanceName;
        }
    }

    //Implement IDisposable.
    public void Dispose()
    {
        Console.WriteLine("\n[{0}].Base.Dispose()", instanceName);
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Free other state (managed objects).
                Console.WriteLine("[{0}].Base.Dispose(true)", instanceName);
                trackingList.Remove(this);
                Console.WriteLine("[{0}] Removed from tracking list: {1:x16}",
                    instanceName, this.GetHashCode());
            }
            else
            {
                Console.WriteLine("[{0}].Base.Dispose(false)", instanceName);
            }
            disposed = true;
        }
    }

    // Use C# destructor syntax for finalization code.
    ~Base()
    {
        // Simply call Dispose(false).
        Console.WriteLine("\n[{0}].Base.Finalize()", instanceName);
        Dispose(false);
    }
}

// Design pattern for a derived class.
public class Derived : Base
{
    private bool disposed = false;
    private IntPtr umResource;

    public Derived(string instanceName, List<object> tracking) :
        base(instanceName, tracking)
    {
         // Save the instance name as an unmanaged resource
         umResource = Marshal.StringToCoTaskMemAuto(instanceName);
    }

    protected override void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                Console.WriteLine("[{0}].Derived.Dispose(true)", InstanceName);
                // Release managed resources.
            }
            else
            {
                Console.WriteLine("[{0}].Derived.Dispose(false)", InstanceName);
            }
            // Release unmanaged resources.
            if (umResource != IntPtr.Zero)
            {
                Marshal.FreeCoTaskMem(umResource);
                Console.WriteLine("[{0}] Unmanaged memory freed at {1:x16}",
                    InstanceName, umResource.ToInt64());
                umResource = IntPtr.Zero;
            }
            disposed = true;
        }
        // Call Dispose in the base class.
        base.Dispose(disposing);
    }
    // The derived class does not have a Finalize method
    // or a Dispose method without parameters because it inherits
    // them from the base class.
}

public class TestDisposal
{
    public static void Main()
    {
        List<object> tracking = new List<object>();

        // Dispose is not called, Finalize will be called later.
        using (null)
        {
            Console.WriteLine("\nDisposal Scenario: #1\n");
            Derived d3 = new Derived("d1", tracking);
        }

        // Dispose is implicitly called in the scope of the using statement.
        using (Derived d1 = new Derived("d2", tracking))
        {
            Console.WriteLine("\nDisposal Scenario: #2\n");
        }

        // Dispose is explicitly called.
        using (null)
        {
            Console.WriteLine("\nDisposal Scenario: #3\n");
            Derived d2 = new Derived("d3", tracking);
            d2.Dispose();
        }

        // Again, Dispose is not called, Finalize will be called later.
        using (null)
        {
            Console.WriteLine("\nDisposal Scenario: #4\n");
            Derived d4 = new Derived("d4", tracking);
        }

        // List the objects remaining to dispose.
        Console.WriteLine("\nObjects remaining to dispose = {0:d}", tracking.Count);
        foreach (Derived dd in tracking)
        {
            Console.WriteLine("    Reference Object: {0:s}, {1:x16}",
                dd.InstanceName, dd.GetHashCode());
        }

        // Queued finalizers will be exeucted when Main() goes out of scope.
        Console.WriteLine("\nDequeueing finalizers...");
    }
}

// The program will display output similar to the following:
//
// Disposal Scenario: #1
//
//
// Disposal Scenario: #2
//
//
// [d2].Base.Dispose()
// [d2].Derived.Dispose(true)
// [d2] Unmanaged memory freed at 000000000034e420
// [d2].Base.Dispose(true)
// [d2] Removed from tracking list: 0000000002bf8098
//
// Disposal Scenario: #3
//
//
// [d3].Base.Dispose()
// [d3].Derived.Dispose(true)
// [d3] Unmanaged memory freed at 000000000034e420
// [d3].Base.Dispose(true)
// [d3] Removed from tracking list: 0000000000bb8560
//
// Disposal Scenario: #4
//
//
// Objects remaining to dispose = 2
//    Reference Object: d1, 000000000297b065
//    Reference Object: d4, 0000000003553390
//
// Dequeueing finalizers...
//
// [d4].Base.Finalize()
// [d4].Derived.Dispose(false)
// [d4] Unmanaged memory freed at 000000000034e420
// [d4].Base.Dispose(false)
//
// [d1].Base.Finalize()
// [d1].Derived.Dispose(false)
// [d1] Unmanaged memory freed at 000000000034e3f0
// [d1].Base.Dispose(false)

Para obtener un ejemplo de código adicional que muestra el modelo de diseño para implementar *** Finalize *** y *** Dispose ***, vea *** Implementing a Dispose Method ***.

Personalizar el nombre del método Dispose

En algunos casos, es más adecuado utilizar un nombre específico del dominio que utilizar Dispose. Por ejemplo, en la encapsulación de un archivo se puede utilizar Close como el nombre del método. En este caso, se implementa Dispose de forma privada y se crea un método Close público que llama a Dispose. En el siguiente ejemplo de código se muestra este modelo. Se puede reemplazar Close con un nombre de método adecuado al dominio. Este ejemplo requiere el espacio de nombres System.

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

Finalize

En las reglas siguientes se describen las instrucciones de uso del método Finalize:

  • Implemente Finalize sólo en los objetos que requieran finalización. Hay algunos costos de rendimiento asociados con los métodos Finalize.

  • Si requiere un método Finalize, considere la posibilidad de implementar IDisposable para que los usuarios de la clase puedan evitar el costo de invocar el método Finalize.

  • No haga más visible el método Finalize. Este método debe ser de tipo protected y no de tipo public.

  • El método Finalize de un objeto debe liberar todos los recursos externos que posea el objeto. Además, un método Finalize debe liberar sólo los recursos que contiene el objeto. El método Finalize no debe hacer referencia a ningún otro objeto.

  • No llame directamente al método Finalize de un objeto que no sea de la clase base del objeto. Ésta no es una operación válida en el lenguaje de programación C#.

  • Llame al método Finalize de la clase base desde el método Finalize de un objeto.

    NotaNota

    El método Finalize de la clase base se llama automáticamente con la sintaxis de C# y C++ del destructor.

Dispose

En las reglas siguientes se describen las instrucciones de uso del método Dispose:

  • Implemente el modelo de diseño Dispose en un tipo que encapsule recursos que deben ser liberados explícitamente. Los usuarios pueden liberar recursos externos llamando al método Dispose público.

  • Implemente el modelo de diseño Dispose en un tipo base que habitualmente tiene tipos derivados que contienen recursos, aunque la clase base no contenga ninguno. Si el tipo base contiene un método Close, esto suele indicar que se debe implementar el método Dispose. En este caso, no implemente un método Finalize en el tipo base. Finalize se debe implementar en todos los tipos derivados con recursos que es necesario limpiar.

  • Libere todos los recursos desechables que contenga un tipo en su método Dispose.

  • Una vez llamado el método Dispose en una instancia, evite ejecutar el método Finalize mediante una llamada al método GC.SuppressFinalize. La excepción a esta regla son las situaciones muy poco habituales en las que el trabajo que no realiza el método Dispose se debe hacer con el método Finalize.

  • Llame al método Dispose de la clase base si implementa la interfaz IDisposable.

  • No dé por supuesto que se llamará al método Dispose. Los recursos no administrados que contiene un tipo también se deben liberar en un método Finalize en el caso de que no se llame al método Dispose.

  • Inicie una excepción ObjectDisposedException a partir de los métodos de instancias de este tipo (distintos de Dispose) una vez que se haya deshecho de los recursos. Esta regla no se aplica al método Dispose porque debería ser invocable varias veces sin iniciar una excepción.

  • Propague las llamadas al método Dispose a través de la jerarquía de tipos base. El método Dispose debe liberar todos los recursos que contenga este objeto y cualquier objeto contenido por el mismo. Por ejemplo, puede crear un objeto como TextReader que contenga los objetos Stream y Encoding, los dos creados por TextReader sin el conocimiento del usuario. Además, tanto Stream como Encoding pueden adquirir recursos externos. Cuando llame al método Dispose en TextReader, éste deberá llamar a su vez al método Dispose en los objetos Stream y Encoding, que liberarán los recursos externos.

  • Considere la posibilidad de no permitir que un objeto sea utilizable después de que se haya llamado a su método Dispose. La acción de volver a crear un objeto una vez desechado es un modelo difícil de implementar.

  • Permita que se llame al método Dispose más de una vez sin iniciar excepciones. El método no debe realizar ninguna acción después de la primera llamada.

Portions Copyright 2005 Microsoft Corporation. Reservados todos los derechos.

Portions Copyright Addison-Wesley Corporation. Reservados todos los derechos.

Para obtener más información sobre las directrices de diseño, consulte “las instrucciones de diseño de Framework: Convenciones, frases realizadas y modelos para libro de bibliotecas reutilizables de .NET” de Krzysztof Cwalina y Brad Abrams, publicados por Addison-Wesley, 2005.

Vea también

Referencia

IDisposable.Dispose

Object.Finalize

Conceptos

recolección de elementos no utilizados

Otros recursos

Instrucciones de diseño para desarrollar bibliotecas de clases

Modelos de diseño