Dispose/Finalize degli oggetti in .NET

Ancora oggi ricordo che uno degli shock più grossi che provai nel passaggio VB6->VB.NET fu lo scoprire il modo in cui il garbage collector rilascia gli oggetti allocati dall’heap.

Come tutti i programmatori VB6 (o comunque legati al mondo COM) ero abituato all’allocazione/deallocazione detereministica degli oggetti: in altre parole in VB6 scrivere

Set objectVar = New UserClass

significa allocare l’oggetto nell’heap; per converso, scrivere

Set objectVar = Nothing

significa deallocare l’oggetto nell’heap; l’operazione era talmente “sicuramente instantanea” che l’oggetto avrebbe avuto valore Nothing sin dalla riga successiva.

Le cose cambiano sostanzialmente nel mondo .NET; innanzitutto è sparita la keyword Set (perlomeno in questo contesto), per cui l’inizializzazione di un nuovo oggetto si scriverà semplicemente:

objectVar = New UserClass

Molto meglio, così, senza quel fastidiosissimo Set, vero? Scherzi a parte, la vera differenza sta nella finalizzazione:

objectVar = Nothing

Uhm, no, non mi riferisco alla sparizione della keyword Set, come per l’inizializzazione. Mi riferisco invece al fatto che questa istruzione fa sì che la rimozione dell’oggetto dalla memoria avvenga sicuramente entro la prossima referenza all’oggetto: ciò implica che l’oggetto in realtà potrebbe essere rilasciata anche diverso tempo dopo.

Il problema non è tanto per l’utilizzatore dell’oggetto: la prossima volta che tento di accedere all’oggetto è garantito che ottenga il valore Nothing; però non posso più fare affidamento sul fatto che il codice di cleanup venga eseguita nella stessa sequenza temporale cui ero abituato in VB6.

Attenzione: il codice viene sicuramente eseguito, ma non posso determinare con precisione quando! Per chi come me era abituato ad inserire del codice di cleanup nell’evento Class_Terminate è stato un piccolo terremoto.

Innanzitutto l’evento non esiste più; al suo posto le classi .NET mettono a disposizione il metodo (Protected) Finalize che può essere sovrascritto con il proprio codice di cleanup

Protected Overrides Sub Finalize()
  Debug.WriteLine("Finalizing objects!")
End Sub

Il codice appena visto permette di notare che ho dovuto usare il metodo WriteLine della classe Debug, anzichè quello più comune della classe Console: ciò è dovuto al fatto che l’oggetto Console potrebbe essere già stato distrutto dal garbage collector.

Francesco Balena nel suo Programmare Microsoft Visual Basic.NET nel descrivere il metodo Finalize esprime il seguente, importantissimo concetto:

Non accedere mai ad un oggetto esterno da una procedura Finalize, poichè l’oggetto potrebbe essere già stato distrutto.

Innanzitutto bisogna rilevare che il garbage collector potrebbe invocare il metodo Finalize dopo che l’oggetto sia stato reso indisponibile all’applicazione: se l’oggetto ha aperto una risorsa non gestita (la clipboard o un file, ad esempio) ciò potrebbe comportare l’impossibilità di accedere alla risorsa da parte di altri processi.

In secondo luogo, gli oggetti che espongono il metodo Finalize richiedono almeno due garbage collection, se non di più, con evidente spreco di risorse.

Per ovviare a questi problemi il Framework .NET mette a disposizione l’interfaccia IDisposable che espone unicamente il metodo Dispose, definito come Public e quindi accessibile anche agli utilizzatori della classe:

Public Class DisposableClass
  Implements IDisposable

  Public Sub Dispose() Implements IDisposable.Dispose
    ' Rilascio delle risorse
  End Sub

End Class

A questo punto possiamo cominciare a pensare ad una nuovo modo per abbinare la Finalize e il Dispose; l’idea di base è di implementare il metodo Dispose dell’interfaccia IDisposable ed il metodo Finalize, in modo che entrambi affidino il codice di clean-up vero e proprio ad un terzo metodo (che qui ho chiamato ancora Dispose, facendo sfoggio di overloading e fantasia); questo metodo verrà invocato dal metodo IDisposable.Dispose con il parametro True (il che provocherà l’esecuzione del codice di cleanup vero e proprio), mentre la Finalize invocherà il metodo Dispose con il parametro False (il che farà sì che il codice di cleanup non venga in realtà eseguito):

#Region " IDisposable"
  ' Codice basato sull'esempio di
  ' F.Balena - Programming Visual Basic.NET - cap.4

  ' Dispose() calls Dispose(true)
  Public Overloads Sub Dispose() Implements IDisposable.Dispose
    Dispose(True)
    GC.SuppressFinalize(Me)

  End Sub 'Dispose

  ' The bulk of the clean-up code is implemented in Dispose(bool)
  Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)
    If disposing Then
      ' free managed resources
      If Not IsNothing(m_Connection) Then
        m_Connection.Dispose()

      End If

    End If

  End Sub 'Dispose

  ' NOTE: Leave out the finalizer altogether if this class doesn't
  ' own unmanaged resources itself, but leave the other methods
  ' exactly as they are.
  Protected Overrides Sub Finalize()
    ' Finalizer calls Dispose(false)
    Dispose(False)
    MyBase.Finalize()

  End Sub 'Finalize

#End Region

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.

*