Condividi tramite


Implementazione di un metodo Dispose

Il modello per eliminare un oggetto, definito come modello Dispose, impone ordine nella durata di un oggetto. Il modello Dispose viene utilizzato solo per gli oggetti che accedono a risorse non gestite. Ciò è dovuto al fatto che il Garbage Collector è molto efficiente nel recupero degli oggetti gestiti inutilizzati.

Lo scopo del metodo Disposedi un tipo è quello di rilasciarne tutte le risorse, nonché di rilasciare tutte le risorse appartenenti ai relativi tipi di base tramite la chiamata al metodo Dispose del tipo padre. Il metodo Dispose del tipo padre deve rilasciare tutte le risorse e chiamare a sua volta il metodo Dispose del proprio tipo padre, propagando questo modello in tutta la gerarchia dei tipi di base. Al fine di garantire la corretta pulitura delle risorse in ogni occasione, deve essere possibile chiamare il metodo Dispose più volte senza che venga generata un'eccezione.

Non c'è vantaggio a livello di prestazioni nell'implementare il metodo Dispose su tipi che utilizzano solo risorse gestite (ad esempio matrici) perché queste vengono recuperate automaticamente dal Garbage Collector. Utilizzare il metodo Dispose soprattutto su oggetti gestiti che utilizzano risorse native e su oggetti COM esposti a .NET Framework. Gli oggetti gestiti che utilizzano risorse native (ad esempio la classe FileStream) implementano l'interfaccia IDisposable.

Nota importanteImportante

Questo argomento non è destinato ai programmatori C++,che possono invece fare riferimento a Destructors and Finalizers in Visual C++.In .NET Framework versione 2.0 il compilatore C++ fornisce il supporto per l'implementazione dell'eliminazione deterministica delle risorse e non consente l'implementazione diretta del metodo Dispose.

Un metodo Dispose deve chiamare il metodo SuppressFinalize per l'oggetto che viene eliminato. Se l'oggetto si trova nella coda di finalizzazione, SuppressFinalize impedisce che venga chiamato il relativo metodo Finalize. Tenere presente che l'esecuzione di un metodo Finalize incide notevolmente sulle prestazioni. Pertanto, se il metodo Dispose ha già eseguito le operazioni di pulitura sull'oggetto, non occorre che il Garbage Collector chiami il relativo metodo Finalize.

L'esempio di codice fornito per il metodo GC.KeepAlive illustra come una procedura di Garbage Collection troppo incisiva possa determinare l'esecuzione di un finalizzatore mentre un membro dell'oggetto recuperato è ancora in esecuzione. È consigliabile chiamare il metodo KeepAlive alla fine di un metodo Dispose di lunga durata.

L'alternativa SafeHandle

La scrittura di codice per il finalizzatore di un oggetto è un'attività complessa che può causare problemi se non eseguita correttamente. È pertanto consigliabile costruire oggetti SafeHandle anziché implementare il modello Dispose.

La classe SafeHandle semplifica i problemi di durata degli oggetti assegnando e rilasciando handle senza interruzione. Contiene un finalizzatore critico la cui esecuzione è garantita durante lo scaricamento di un dominio dell'applicazione. Per ulteriori informazioni sui vantaggi dell'utilizzo di un handle sicuro, vedere Handle protetti e finalizzazione critica.

La classe SafeHandle nello spazio dei nomi System.Runtime.InteropServices è una classe wrapper astratta per gli handle del sistema operativo. La derivazione da questa classe è difficile. Utilizzare invece le classi derivate nello spazio dei nomi Microsoft.Win32.SafeHandles che forniscono handle sicuri per gli elementi seguenti:

  • File e pipe.

  • Visualizzazioni di memoria.

  • Costrutti di crittografia.

  • Chiavi del Registro di sistema.

  • Handle di attesa.

Esempio

Nell'esempio di codice riportato di seguito viene illustrato il modello di progettazione consigliato per l'implementazione di un metodo Dispose per le classi che incapsulano risorse non gestite.

Le classi di risorse sono solitamente derivate da classi native complesse o da API e devono essere personalizzate di conseguenza. Utilizzare questo modello di codice come punto di partenza per creare classi di risorse e personalizzarle opportunamente in base alle risorse incapsulate.

Imports System
Imports System.IO
Class Program

    Public Shared Sub Main()
        Try
            ' Initialize a Stream resource to pass 
            ' to the DisposableResource class.
           Console.Write("Enter filename and its path: ")
            Dim fileSpec As String = Console.ReadLine
            Dim fs As FileStream = File.OpenRead(fileSpec)
            Dim TestObj As DisposableResource = New DisposableResource(fs)

            ' Use the resource.
            TestObj.DoSomethingWithResource()

            ' Dispose theresource.
            TestObj.Dispose()

        Catch e As FileNotFoundException
            Console.WriteLine(e.Message)
        End Try
    End Sub
End Class

' This class shows how to use a disposable resource.
' The resource is first initialized and passed to
' the constructor, but it could also be
' initialized in the constructor.
' The lifetime of the resource does not 
' exceed the lifetime of this instance.
' This type does not need a finalizer because it does not
' directly create a native resource like a file handle
' or memory in the unmanaged heap.
Public Class DisposableResource
    Implements IDisposable

    Private _resource As Stream

    Private _disposed As Boolean

    ' The stream passed to the constructor
    ' must be readable and not null.
    Public Sub New(ByVal stream As Stream)
        MyBase.New()
        If (stream Is Nothing) Then
            Throw New ArgumentNullException("Stream is null.")
        End If
        If Not stream.CanRead Then
            Throw New ArgumentException("Stream must be readable.")
        End If
        _resource = stream
        Dim objTypeName As String = _resource.GetType.ToString
        _disposed = False
    End Sub

    ' Demonstrates using the resource.
    ' It must not be already disposed.
    Public Sub DoSomethingWithResource()
        If _disposed Then
            Throw New ObjectDisposedException("Resource was disposed.")
        End If

        ' Show the number of bytes.
        Dim numBytes As Integer = CType(_resource.Length, Integer)
        Console.WriteLine("Number of bytes: {0}", numBytes.ToString)
    End Sub

    Public Overloads Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)

        ' Use SupressFinalize in case a subclass
        ' of this type implements a finalizer.
        GC.SuppressFinalize(Me)
    End Sub

    Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)
        If Not _disposed Then

            ' If you need thread safety, use a lock around these 
            ' operations, as well as in your methods that use the resource.
            If disposing Then
                If (Not (_resource) Is Nothing) Then
                    _resource.Dispose()
                End If
                Console.WriteLine("Object disposed.")
            End If

            ' Indicates that the instance has been disposed.
            _resource = Nothing
            _disposed = True
        End If
    End Sub
End Class
using System;
using System.IO;

class Program
{

    static void Main()
    {
        try
        {
            // Initialize a Stream resource to pass 
            // to the DisposableResource class.
            Console.Write("Enter filename and its path: ");
            string fileSpec = Console.ReadLine();
            FileStream fs = File.OpenRead(fileSpec);
            DisposableResource TestObj = new DisposableResource(fs);

            // Use the resource.
            TestObj.DoSomethingWithResource();

            // Dispose the resource.
            TestObj.Dispose();

        }
        catch (FileNotFoundException e)
        {
            Console.WriteLine(e.Message);
        }
    }
}


// This class shows how to use a disposable resource.
// The resource is first initialized and passed to
// the constructor, but it could also be
// initialized in the constructor.
// The lifetime of the resource does not 
// exceed the lifetime of this instance.
// This type does not need a finalizer because it does not
// directly create a native resource like a file handle
// or memory in the unmanaged heap.

public class DisposableResource : IDisposable
{

    private Stream _resource;  
    private bool _disposed;

    // The stream passed to the constructor 
    // must be readable and not null.
    public DisposableResource(Stream stream)
    {
        if (stream == null)
            throw new ArgumentNullException("Stream in null.");
        if (!stream.CanRead)
            throw new ArgumentException("Stream must be readable.");

        _resource = stream;

        _disposed = false;
    }

    // Demonstrates using the resource. 
    // It must not be already disposed.
    public void DoSomethingWithResource() {
        if (_disposed)
            throw new ObjectDisposedException("Resource was disposed.");

        // Show the number of bytes.
        int numBytes = (int) _resource.Length;
        Console.WriteLine("Number of bytes: {0}", numBytes.ToString());
    }


    public void Dispose() 
    {
        Dispose(true);

        // Use SupressFinalize in case a subclass
        // of this type implements a finalizer.
        GC.SuppressFinalize(this);      
    }

    protected virtual void Dispose(bool disposing)
    {
        // If you need thread safety, use a lock around these 
        // operations, as well as in your methods that use the resource.
        if (!_disposed)
        {
            if (disposing) {
                if (_resource != null)
                    _resource.Dispose();
                    Console.WriteLine("Object disposed.");
            }

            // Indicate that the instance has been disposed.
            _resource = null;
            _disposed = true;   
        }
    }
}

Vedere anche

Riferimenti

SuppressFinalize

Destructors and Finalizers in Visual C++

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

Concetti

Override del metodo Finalize