Condividi tramite


Implementazione dei metodi Finalize e Dispose per la pulizia delle risorse non gestite

NotaNota

Per informazioni sulla fine e l'eliminazione delle risorse utilizzando C++, vedere Destructors and Finalizers in Visual C++.

Le istanze delle classi spesso incapsulano il controllo sulle risorse che non sono gestite dall'ambiente di esecuzione, quali handle delle finestre (HWND), connessioni al database e così via. È necessario quindi garantire un modo esplicito e uno implicito per liberare tali risorse. Fornire il controllo implicito implementando il metodo Finalize protetto su un oggetto (sintassi del distruttore in C# e C++). Il Garbage Collector chiama questo metodo in un momento qualsiasi, una volta determinata la mancanza di eventuali ulteriori riferimenti validi all'oggetto.

In alcuni casi è possibile consentire ai programmatori che utilizzano un oggetto di rilasciare in modo esplicito le risorse esterne prima che il Garbage Collector liberi l'oggetto. Se una risorsa esterna è scarsa o costosa, è possibile ottenere prestazioni migliori se il programmatore rilascia le risorse in modo esplicito quando non vengono più utilizzate. Per disporre di un controllo esplicito, implementare Dispose fornito da IDisposable. Una volta terminato l'utilizzo dell'oggetto, il consumer deve chiamare questo metodo. È possibile chiamare il metodo Dispose anche se sono attivi altri riferimenti all'oggetto.

Si noti che anche quando si fornisce il controllo esplicito utilizzando il metodo Dispose, è necessario fornire la pulitura implicita utilizzando il metodo Finalize. Il metodo Finalize fornisce un meccanismo di backup per evitare che le risorse vengano perdute definitivamente se non viene eseguita correttamente la chiamata al metodo Dispose.

Per ulteriori informazioni sull'implementazione dei metodi Finalize e Dispose per la pulitura delle risorse non gestite, vedere Garbage Collection. Nell'esempio seguente viene illustrato il modello di progettazione di base per l'implementazione Dispose. Per questo esempio è necessario lo spazio dei nomi 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.
}

Il codice seguente consente di espandere l'esempio precedente in modo da visualizzare i diversi modi in cui Dispose viene richiamato e quando viene chiamato il Finalize. Le fasi del modello eliminando tracciati con l'output nella console. L'allocazione e il rilascio di una risorsa non gestita viene gestita nella classe derivata.

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)

Per un esempio di codice aggiuntivo che viene illustrato il modello di progettazione per l'implementazione di Finalize e Dispose, vedere Implementazione di un metodo Dispose.

Personalizzazione del nome di un metodo Dispose

In alcuni casi è più appropriato utilizzare per il dominio un nome specifico anziché Dispose. Per l'incapsulamento di un file, ad esempio, è possibile utilizzare il nome di metodo Close. In questo caso, implementare il metodo Dispose in modo privato e creare un metodo Close pubblico che chiami il metodo Dispose. Nell'esempio di codice seguente viene illustrato questo modello. È possibile sostituire Close con il nome di un metodo appropriato per il dominio. Per questo esempio è necessario lo spazio dei nomi 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

Di seguito sono riportate le linee guida per l'utilizzo del metodo Finalize:

  • Implementare il metodo Finalize solo su oggetti che richiedono il completamento. Ai metodi Finalize vengono associati alcuni costi in termini di prestazioni.

  • Se è necessario utilizzare un metodo Finalize, è possibile implementare IDisposable per evitare agli utenti della classe la necessità di richiamare il metodo Finalize.

  • Non rendere il metodo Finalize più visibile. Il metodo deve essere protetto e non pubblico.

  • Il metodo Finalize di un oggetto deve essere libero da qualsiasi risorsa esterna di proprietà dell'oggetto. Deve inoltre liberare solo le risorse mantenute dall'oggetto. Il metodo Finalize non deve far riferimento ad altri oggetti.

  • Non chiamare direttamente un metodo Finalize su un oggetto diverso dalla classe base dell'oggetto. Questa operazione non è valida nel linguaggio di programmazione C#.

  • Chiamare il metodo Finalize della classe base dal metodo Finalize di un oggetto.

    NotaNota

    Il metodo Finalize della classe base viene chiamato automaticamente insieme alla sintassi dei distruttori C# e C++.

Dispose

Di seguito sono riportate le linee guida per l'utilizzo del metodo Dispose:

  • Implementare il modello di progettazione del metodo Dispose su un tipo che incapsula le risorse che devono essere liberate in modo esplicito. Gli utenti possono liberare le risorse esterne chiamando il metodo Dispose pubblico.

  • Implementare il modello di progettazione del metodo Dispose su un tipo base che in genere dispone di tipi derivati che contengono le risorse anche se il tipo base non le contiene. Se il tipo base dispone di un metodo Close, è probabile che sia necessario implementare il metodo Dispose. In questi casi non implementare un metodo Finalize sul tipo base. È necessario implementare Finalize in qualsiasi tipo derivato che introduce risorse che richiedono la pulitura.

  • Liberare le risorse eliminabili che possiede un tipo nel proprio metodo Dispose.

  • Dopo aver chiamato Dispose su un'istanza, impedire l'esecuzione del metodo Finalize chiamando invece GC.SuppressFinalize. L'eccezione a tale regola è rappresentata dai rari casi in cui è necessario eseguire operazioni nel metodo Finalize che non rientrano nel metodo Dispose.

  • Chiamare il metodo Dispose della classe base se viene implementata l'interfaccia IDisposable.

  • Non presupporre che verrà chiamato il metodo Dispose. Le risorse non gestite appartenenti a un tipo devono essere liberate in un metodo Finalize nel caso in cui il metodo Dispose non venga chiamato.

  • Generare un'eccezione ObjectDisposedException dai metodi di istanza del tipo, tranne Dispose, se le risorse sono già state eliminate. Questa regola non viene applicata al metodo Dispose, che deve poter essere chiamato più volte senza che venga generata un'eccezione.

  • Propagare le chiamate al metodo Dispose attraverso la gerarchia dei tipi base. Il metodo Dispose deve liberare tutte le risorse utilizzate da questo oggetto e da qualsiasi oggetto appartenente a quest'ultimo. È possibile, ad esempio, creare un oggetto quale TextReader contenente sia Stream che Encoding, entrambi creati automaticamente dall'oggetto TextReader. Sia Stream che Encoding possono inoltre acquisire risorse esterne. Quando si chiama il metodo Dispose su TextReader, è necessario che quest'ultimo chiami a sua volta il metodo Dispose su Stream e Encoding affinché liberino le rispettive risorse esterne.

  • Dopo che è stato chiamato il metodo Dispose relativo a un oggetto, può essere opportuno impedire l'utilizzo dell'oggetto. La rigenerazione di un oggetto già eliminato è un modello di difficile implementazione.

  • Consentire più chiamate di un metodo Dispose senza che venga generata un'eccezione. Il metodo non deve eseguire alcuna operazione dopo la prima chiamata.

Portions Copyright 2005 Microsoft Corporation. Tutti i diritti riservati.

Portions Copyright Addison-Wesley Corporation. Tutti i diritti riservati.

Per ulteriori informazioni sulle linee guida di progettazione, vedere “le linee guida di progettazione di Framework: Idiomi convenzioni, e modelli per libro raccolte riutilizzabili .NET„ di Krzysztof Cwalina e brad Abrams, emessi da Addison-Wesley, 2005.

Vedere anche

Riferimenti

IDisposable.Dispose

Object.Finalize

Concetti

Garbage Collection

Altre risorse

Linee guida di progettazione per lo sviluppo di librerie di classi

Modelli di progettazione