Best practice per l'utilizzo di Dispose e Finalize in .Net

Microsoft .Net Framework fornisce un garbage collector che viene eseguito in background e rilascia la memoria occupata dagli oggetti gestiti quando non sono più referenziati nel codice. Sebbene il garbage collector sia abile nel ripulire la memoria occupata dagli oggetti gestiti, non è garantito che la memoria occupata da oggetti non gestiti venga ripulita quando viene eseguito il successivo ciclo GC. Se nell'applicazione sono presenti risorse non gestite, è necessario assicurarsi di rilasciare tali risorse in modo esplicito quando si è finito di utilizzarle. In questo articolo, evidenzierò le migliori pratiche da seguire per pulire le risorse utilizzate nella tua applicazione.

Il GC utilizza le generazioni per mantenere e gestire la durata relativa degli oggetti creati nella memoria. Gli oggetti creati nuovi vengono inseriti nella generazione 0. Il presupposto di base è che un oggetto appena creato potrebbe avere una durata più breve mentre un oggetto vecchio potrebbe avere una durata più lunga. Quando gli oggetti che risiedono nella generazione 0 non vengono recuperati dopo un ciclo GC, vengono spostati nella generazione 1. Allo stesso modo, se gli oggetti che risiedono nella generazione 1 sopravvivono a una pulizia GC, vengono spostati nella generazione 2. Si noti che il GC viene eseguito più frequentemente nel generazioni inferiori che in quelle superiori. Quindi, gli oggetti che risiedono nella generazione 0 verrebbero puliti più frequentemente rispetto agli oggetti che risiedono nella generazione 1. Quindi,è una pratica di programmazione migliore assicurarsi di utilizzare più oggetti locali che oggetti nello scope più alto per evitare che gli oggetti vengano spostati a generazioni superiori.

Nota che quando hai un distruttore nella tua classe, il runtime lo tratta come un metodo Finalize (). Poiché la finalizzazione è costosa, dovresti usare i distruttori solo se necessario, quando hai alcune risorse nella tua classe che avresti bisogno di pulire. Quando hai un finalizzatore nella tua classe, gli oggetti di quelle classi vengono spostati nella coda di finalizzazione. Se gli oggetti sono raggiungibili, vengono spostati nella coda "Freachable". Il GC recupera la memoria occupata dagli oggetti che non sono raggiungibili. Periodicamente, il GC controlla se gli oggetti che risiedono nella coda "Freachable" sono raggiungibili. Se non sono raggiungibili, la memoria occupata da quegli oggetti viene recuperata. Quindi, è evidente che gli oggetti che risiedono nella coda "Freachable" avrebbero bisogno di più tempo per essere ripuliti dal garbage collector.È una cattiva pratica avere distruttori vuoti nella propria classe C # poiché gli oggetti per tali classi verrebbero spostati nella coda di finalizzazione e, se necessario, nella coda "Freachable".

Un finalizzatore viene chiamato implicitamente quando la memoria occupata dall'oggetto viene recuperata. Tuttavia, non è garantito che un finalizzatore venga chiamato dal GC: può o non può essere chiamato affatto. In sostanza, un finalizzatore funziona in una modalità non deterministica: il runtime non garantisce affatto che venga chiamato un finalizzatore. È tuttavia possibile forzare la chiamata del finalizzatore anche se non è affatto una buona pratica in quanto sono associate penalità di prestazione. I finalizzatori devono essere sempre protetti e devono essere sempre utilizzati solo per pulire le risorse gestite. Non dovresti mai allocare memoria all'interno del finalizzatore, scrivere codice per implementare la sicurezza dei thread o richiamare metodi virtuali dall'interno di un finalizzatore.

Il metodo Dispose fornisce invece un approccio di "pulizia deterministica" verso la pulizia delle risorse in .Net. Tuttavia, il metodo Dispose a differenza del finalizzatore dovrebbe essere chiamato in modo esplicito. Se si dispone di un metodo Dispose definito in una classe, è necessario assicurarsi che venga chiamato. Quindi, il metodo Dispose dovrebbe essere chiamato esplicitamente dal codice client. Ma cosa succede se dimentichi di chiamare il metodo Dispose esposto da una classe che utilizza risorse non gestite? I client di un'istanza di una classe che implementa l'interfaccia IDisposable devono chiamare il metodo Dispose in modo esplicito. In questo caso, devi chiamare Dispose dall'interno del finalizzatore. Questa strategia di finalizzazione deterministica automatica garantisce che le risorse non gestite utilizzate nel codice vengano ripulite.

Dovresti implementare IDisposable su ogni tipo che ha un finalizzatore. Si consiglia di implementare sia Dispose che Finalize quando si hanno risorse non gestite nella classe.

Il frammento di codice seguente illustra come implementare il pattern Dispose Finalize in C #.

vuoto virtuale protetto Dispose (bool disposing)

        {

            if (smaltimento)

            {

                // scrive il codice per pulire gli oggetti gestiti

            }

            // scrive il codice per pulire oggetti e risorse non gestiti

        }

Questo metodo Dispose parametrizzato può essere chiamato automaticamente dal distruttore come mostrato nel frammento di codice di seguito.

  ~ Risorse ()

        {

            se (! smaltito)

            {

                disposto = vero;

                Smaltire (falso);

            }

        }