Dispose/Finalize degli oggetti
pubblicato il 06/11/2006
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ì, eh?! Scherzi a parte, la vera differenza sta nella finalizzazione:
objectVar = Nothing
Uhm, no, non mi riferivo 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
