2009-07-28 15 views
5

La mia classe contiene un oggetto da Interop e chiama un metodo su di esso che lo induce ad allocare roba. Espone anche un metodo per liberare quella roba, quindi mi aspetto che dovrei chiamare questo in Dispose():Come si implementa il pattern di eliminazione in C# quando si avvolge un oggetto COM Interop?

class MyClass : IDisposable 
{ 
    private DllName.ComClassName comInstance; 

    void SomeMethod() 
    { 
     comInstance = new DllName.ComClassName(); 
     comInstance.AllocStuff(); 
    } 

    public void Dispose() 
    { 
     comInstance.FreeThatStuff(); 
    } 
} 

Ora, dovrei espandi tutto quello di seguire il pattern Dispose. Non ho altre risorse monouso o non gestite per rilasciare, così assumendo comInstance è gestito (non è che quello che fa Interop, avvolge non gestito in gestito?), Penso che i disolves modello per:

public void Dispose() 
{ 
    if (comInstance != null) 
    { 
     comInstance.FreeStuff(); 
     comInstance = null; 
    } 
} 

Quali perdite a meno che non chiamare esplicitamente Dispose() su istanze di MyClass, che renderebbe il pattern Dispose difettoso? Quindi vuol dire comInstance deve essere gestito, e il modello disolves a:

public void Dispose() 
{ 
    DisposeComInstance(); 
    GC.SuppressFinalize(this); 
} 

~MyClass() 
{ 
    DisposeComInstance(); 
} 

private void DisposeComInstance() 
{ 
    if (comInstance != null) 
    { 
     comInstance.FreeStuff(); 
     comInstance = null; 
    } 
} 

EDIT:

  1. Per evitare di ingombrare la mia classe con il modello completo, potrei solo suggellare la mia classe?
  2. Come faccio a sapere che ComClassName (e in generale qualsiasi classe) non è gestito?

risposta

2

Sembra quasi che l'abbiate fatto inchiodare. Ritornerei al pattern in cui disponi di un sistema di smaltimento virtuale protetto che accetta un parametro booleano che indica se gli elementi gestiti devono essere smaltiti. In questo modo, qualcuno che ti seguirà continuerà con lo implement IDisposable properly.

public void Dispose() 
{ 
    this.Dispose(true); 
    GC.SuppressFinalize(this); 
} 

~MyClass() 
{ 
    this.Dispose(false); 
} 

protected virtual void Dispose(bool disposing) 
{ 
    // if (disposing) 
    // { 
    //  // Managed 
    // } 

    if (comInstance != null) 
    { 
     comInstance.FreeStuff(); 
     comInstance = null; 
    } 

    // base.Dispose(disposing) if required 
} 
+1

Meta-commento: assicurati che il tuo Dispose (bool) possa tollerare di essere chiamato più volte di seguito. Prova anche a minimizzare la possibilità che venga lanciata un'eccezione (non sempre possibile). – user7116

+1

Commento meta-meta: oltre a questo, è importante non acquisire mai blocchi o utilizzare il blocco durante la pulizia non gestita. – womp

3

definitiva si vuole questo tipo di modello:

public void Dispose() 
{ 
    Dispose(true); 
    GC.SuppressFinalize(this); 
} 

~MyClass() 
{ 
    Dispose(false); 
} 

private void Dispose(bool disposing) 
{ 
    if (disposing) 
    { 
     // Dispose of disposable objects here 

    } 

    // Other unmanaged cleanup here which will be called by the finalizer 
    if (comInstance != null) 
    { 
     comInstance.FreeStuff(); 
     comInstance = null; 
    } 

    // Call base dispose if inheriting from IDisposable class. 
    base.Dispose(true); 
} 

Per un grande articolo sul perché, controlla Implementing IDisposable and the Dispose pattern properly.

+0

-1, è necessario spostare gli oggetti non gestiti al di fuori del blocco if (disposing). Unmanaged dovrebbe sempre essere pulito. – user7116

+0

Gah! Hai ragione, l'ho copiato da parte del mio codice che utilizzava componenti con codice non gestito ma implementato come dispose e modificato nell'esempio dell'OP ... incolla per la perdita. Fisso. – womp

+0

+1, correttamente implementato ;-D – user7116

3

In primo luogo, sono d'accordo con coloro che suggeriscono avere un finalizzatore come backup , ma cerca di evitare che venga chiamato chiamando esplicitamente myClass.Dispose o 'using'.

ad es.

var myClass = new MyClass() 
try 
{ 
    //do stuff 
} 
finally 
{ 
    myClass.Dispose(); 
} 

o

using (var myClass = new MyClass()) 
{ 
    //do stuff 
} 

Marshall.ReleaseComObject

Se si utilizza un sacco di oggetti COM, ho anche suggerisco di usare Mashall.ReleaseComObject (ComObj) per pulire in modo esplicito riferimenti a il RCW.

Quindi, il codice come questo ha suggerito altrove:

if (comInstance != null) 
{ 
    comInstance.FreeStuff(); 
    comInstance = null; 
} 

sarebbe diventato:

if (comInstance != null) 
{ 
    comInstance.FreeStuff(); 

    int count = Marshall.ReleaseComObject(comInstance); 
    if (count != 0) 
    { 
      Debug.Assert(false, "comInstance count = " + count); 
      Marshal.FinalReleaseComObject(comInstance); 
    } 

    comInstance = null; 
} 

Durante il controllo il valore di ritorno di ReleaseComObject() non è strettamente necessario, mi piace controllare per assicurati che le cose vengano incrementate/decrementate come previsto.

La regola 2-dot

Se si decide di utilizzare questo, qualcosa di cui essere consapevoli è che un certo codice potrebbe essere necessario essere riscritta per rilasciare correttamente gli oggetti COM. Uno in particolare è quello che io chiamo la regola dei 2 punti. Qualsiasi linea che utilizza oggetti COM che contengono 2 punti richiede molta attenzione. Ad esempio,

var name = myComObject.Address.Name; 

In questa dichiarazione si ottiene un riferimento all'oggetto Indirizzo COM incrementare il conteggio dei riferimenti RCW, ma non hanno l'opportunità di chiamare ReleaseComObject. Un modo migliore per farlo sarebbe di scomposizione (try..finally omesso per chiarezza):

var address = myComObject.Address; 
var name = address.Name; 
MyReleaseComObject(address); 

dove MyReleaseComObject è un metodo di utilità confezionamento mia verifica del conteggio e FinalReleaseComObject() dall'alto.

Problemi correlati