2009-09-13 16 views
20

Non penso che questa domanda sia stata posta prima. Sono un po 'confuso sul modo migliore per implementare IDisposable su una classe chiusa, in particolare, una classe sigillata che non eredita da una classe base. (Cioè, una "classe sigillata pura" che è il mio termine inventato.)Implementazione IDisposable su una classe sigillata

Forse alcuni di voi sono d'accordo con me in quanto le linee guida per l'implementazione di IDisposable sono molto confuse. Detto questo, voglio sapere che il modo in cui intendo implementare IDisposable è sufficiente e sicuro.

Sto facendo un po 'di codice P/Invoke che assegna uno IntPtr tramite Marshal.AllocHGlobal e, naturalmente, voglio eliminare in modo pulito la memoria non gestita che ho creato. Così sto pensando a qualcosa di simile

using System.Runtime.InteropServices; 

[StructLayout(LayoutKind.Sequential)] 
public sealed class MemBlock : IDisposable 
{ 
    IntPtr ptr; 
    int length; 

    MemBlock(int size) 
    { 
      ptr = Marshal.AllocHGlobal(size); 
      length = size; 
    } 

    public void Dispose() 
    { 
      if (ptr != IntPtr.Zero) 
      { 
       Marshal.FreeHGlobal(ptr); 
       ptr = IntPtr.Zero; 
       GC.SuppressFinalize(this); 
      } 
    } 

    ~MemBlock() 
    { 
      Dispose(); 
    }  
} 

sto supponendo che perché MemBlock è completamente sigillato e mai deriva da un'altra classe che l'attuazione di un virtual protected Dispose(bool disposing) non è necessario.

Inoltre, il finalizzatore è strettamente necessario? Tutti i pensieri sono benvenuti

risposta

13

Il finalizzatore è necessario come meccanismo di fallback per liberare risorse non gestite se si è dimenticato di chiamare Dispose.

No, non si deve dichiarare un metodo virtual in una classe sealed. Non si compilerebbe affatto. Inoltre, non è consigliabile dichiarare nuovi membri protected nelle classi sealed.

+0

Ma ovviamente nel caso in cui una classe sigillata derivata da una classe base, allora sarebbe necessario un Dispose virtuale - corretto? – zebrabox

+0

Inoltre. Il finalizzatore indica l'aggiunta a una coda di finalizzazione e il sovraccarico di un doppio raccoglitore di rifiuti effettivo. Sembra una pesante penalità da pagare per l'utilizzo di risorse non gestite. Non c'è modo di evitare il colpo di performance? – zebrabox

+0

In tal caso, dovrai "sovrascrivere" il metodo. Non puoi dichiarare alcun metodo in una classe 'sealed' come' virtuale'. È un errore del compilatore **. –

7

Una piccola aggiunta; nel caso generale, un modello comune è quello di avere un metodo Dispose(bool disposing), in modo da sapere se ci si trova in Dispose (dove sono disponibili più cose) rispetto al finalizzatore (in cui non si dovrebbe toccare alcun altro oggetto gestito connesso) .

Ad esempio:

public void Dispose() { Dispose(true); } 
~MemBlock() { Dispose(false); } 
void Dispose(bool disposing) { // would be protected virtual if not sealed 
    if(disposing) { // only run this logic when Dispose is called 
     GC.SuppressFinalize(this); 
     // and anything else that touches managed objects 
    } 
    if (ptr != IntPtr.Zero) { 
      Marshal.FreeHGlobal(ptr); 
      ptr = IntPtr.Zero; 
    } 
} 
+0

Sì, buon punto Marc, ma se sapessi che stavo solo smaltendo risorse non gestite, è strettamente necessario? – zebrabox

+0

Inoltre, domanda molto stupida, ma se il modello Dispose serve a determinare in modo deterministico le risorse non gestite, perché dovrei voler gestire le risorse gestite usa e getta quando devono essere ripulite dal GC? – zebrabox

+0

Se sei deterministico, dovresti pulire tutto ciò che stai * incapsulando *; soprattutto se sono essi stessi "IDisposable". Non * lo faresti * nel finalizzatore, poiché potrebbero essere già stati raccolti (e: non è più il tuo lavoro). E hai ragione; in questo caso, oltre al 'SuppressFinalize' (che non importa molto) non stiamo gestendo nulla, quindi sarebbe bene non disturbare; ecco perché ho enfatizzato il caso * generale *. –

7

Da Joe Duffy's Weblog:

Per le classi sigillate, questo schema bisogno non essere seguito, il che significa che dovrebbe semplicemente implementare la Finalizer e Smaltire con i metodi semplici (ovvero ~ T() (Finalizza) e Dispose() in C#). Quando si sceglie la seconda rotta, il proprio codice deve ancora attenersi alle seguenti linee guida relative alla finalizzazione e alla logica di disputa .

Quindi sì, dovresti essere bravo.

Hai bisogno del finalizzatore come menzionato da Mehrdad. Se vuoi evitarlo, potresti dare un'occhiata a SafeHandle. Non ho abbastanza esperienza con P/Invoke per suggerire l'uso corretto.

+0

Grazie a TrueWill! Ho guardato SafeHandle e secondo Eric Lippert è stato visto dal team BCL come uno dei vantaggi più significativi introdotti in "Whidbey" (non trovo il link per ora). Sfortunatamente è una classe astratta quindi devi eseguire il rollover per ogni situazione che fa schifo – zebrabox

+1

@zebrabox: anche se potresti dover eseguire il rollover in alcune situazioni, la documentazione afferma: "Un set di classi pre-scritte derivate da SafeHandle è fornito come derivazioni astratte e questo set si trova nello spazio dei nomi Microsoft.Win32.SafeHandles. " – TrueWill

+1

@TrueWill. Sì, è vero, ma solo per cose come File Handles, Wait Handles, Pipe Handles e un sacco di roba da cripta. Ancora meglio di niente! – zebrabox

1

Non è possibile dichiarare metodi virtuali in una classe sigillata. Anche dichiarare membri protetti in una classe sigillata ti dà un avvertimento del compilatore. Quindi l'hai implementato correttamente. Chiamare GC.SuppressFinalize (questo) dall'interno del finalizzatore non è necessario per ovvi motivi, ma non può recare danno.

Avere un finalizzatore è essenziale quando si ha a che fare con risorse non gestite, perché non vengono liberate automaticamente, devi farlo nel finalizzatore con viene chiamato automaticamente dopo che l'oggetto è stato garbage collocato.

Problemi correlati