Sinteticamente, il GC si occupa di gestire la rimozione dalla memoria degli oggetti che non sono più utilizzati, ossia quegli oggetti per i quali non è più presente un riferimento attivo (es. una variabile) o che in ogni caso non sono più raggiungibili.
Questi oggetti, assieme a quelli eventualmente creati come dipendenze, se istanziati dal CLR sono "risorse gestite" che quindi vengono distrutte dal GC, come predetto.
Quando si parla di risorse "non gestite" invece, ci si riferisce a tutte quelle risorse che non fanno parte del CLR o che sono diverse da una banale istanza di un oggetto che deve essere semplicemente deallocato al termine del suo utilizzo: pensa a tutti quei casi in cui apri una connessione al DB, oppure un file, o un socket, in generale a tutti quei casi in cui stai istanziando un oggetto (del CLR) che ti permette di interagire con qualcosa di "esterno", fornito dal sistema operativo come servizio o da qualcos'altro.
Per queste ultime risorse viene posto l'accento sulla necessità di rilasciarle il prima possibile. Cosa vuol dire? Che sebbene il GC si occupi di distruggere autonomamente l'oggetto creato nell'ecosistema .NET, deve essere tua cura fare in modo che - se quell'oggetto gestisce una risorsa esterna - questo la chiuda, la rilasci o la liberi il prima possibile, senza attendere l'intervento del GC (che comunque procederà all'operazione al posto tuo, se non hai eseguito questa finalizzazione).
Gli oggetti che gestiscono risorse esterne (es.
FileStream,
SqlConnection, ecc.) hanno quindi generalmente un metodo che consente di rilasciare la risorsa sottostante (es.
Close()): questo vuol dire che il file o la connessione saranno "chiusi", liberando tali risorse nel sistema (es. permettendo ad altri di collegarsi al DB) ma l'oggetto che consente di interoperare con loro rimarrà ancora in memoria e verrà liberato dal GC quando possibile, assieme a tutti gli altri.
Il pattern che deriva dall'implementazione dell'interfaccia
IDisposable consente di identificare tutti quegli oggetti che gestiscono risorse esterne e che implementano quindi un metodo
Dispose() da invocare esplicitamente quando la risorsa non serve più. Ad esempio, per le classi indicate sopra, la chiamata a
Dispose() si traduce in una chiamata a
Close() per chiudere il file e/o la connessione.
In genere quindi si tende a usare il costrutto
try...finally o ancora meglio
using() per "circondare" il codice che fa uso di un oggetto in grado di gestire una risorsa esterna: al termine del blocco, viene richiamato in automatico il metodo
Dispose() per rilasciare la risorsa:
using (var obj = new DisposableClass())
{
// TODO: Codice che usa l'oggetto...
} // Chiamata automatica a Dispose() alla fine
// ...il GC interverrà per rilasciare l'oggetto quando ne ha voglia... :)
Il metodo
Dispose() non va confuso con "finalizzatore"
Finalize(): questo metodo è la controparte del costruttore e viene chiamato dal GC nel momento in cui è necessario distruggere un oggetto, prima di rimuoverlo.
Molte classi che gestiscono risorse esterne possiedono questo metodo e si accertano nel finalizzatore che la risorsa sia stata rilasciata (es. chiusura del file o della connessione) prima di uccidere definitivamente l'oggetto. In pratica, chiamano
Dispose() se non è già stato chiamato.
Nel
Dispose() è frequente trovare una chiamata al metodo
GC.SuppressFinalizer() che permette di indicare al GC che, in fase di distruzione, non deve invocare il finalizzatore in quanto la risorsa esterna è stata già chiusa e gestita tramite chiamata a
Dispose(): questo permette di risparmiare tempo prezioso ed evitare operazioni inutili nella fase di rilascio della memoria occupata dagli oggetti da parte del GC, anche perché quando questo avviene il programma risulta bloccato, quindi deve essere una operazione il più performante possibile.
Riassumendo, è bene lasciare al GC il compito di gestire in autonomia la deallocazione delle istanze, anzi non è possibile fare altrimenti.
Per alcuni oggetti che però gestiscono risorse esterne al framework, occorre curarsi di rilasciarle appena dopo l'uso, in modo che il GC continui solamente a gestire l'oggetto in sé e restituendo quanto prima al sistema la risorsa occupata, per evitare di incappare in un loro esaurimento (es. RAM finita, connessioni max occupate, ecc.).
Se lo sviluppatore però si dimentica di fare questo, la presenza del finalizzatore garantisce comunque che, alla distruzione dell'oggetto, la risorsa verrà comunque rilasciata, quindi in linea generale il sistema risulterà stabile in ogni caso (nei limiti del possibile).
Prova a leggere un po' di documentazione sui metodi citati se vuoi ulteriori approfondimenti o chiarire altri dubbi.
Altrimenti chiedi.
Ciao!