2009-07-28 12 views
5

Mi clase contiene un objeto de Interop y llama a un método que le permite asignar cosas. También expone un método para liberar esas cosas, así que espero que debería informar de esto a Dispose():¿Cómo implemento el patrón de disposición en C# al envolver un objeto COM de Interop?

class MyClass : IDisposable 
{ 
    private DllName.ComClassName comInstance; 

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

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

Ahora, debería ampliar todo lo que de seguir el patrón Dispose. No tengo otros recursos desechables o no administrados para liberar, por lo que suponiendo comInstance es gestionado (no es que lo hace de interoperabilidad, envolturas no administrado en gestionados?), Creo que los disolves patrón a:

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

Que escape a menos que yo Llamar explícitamente a Dispose() en instancias de MyClass, lo que haría que el patrón Dispose sea defectuoso. ¿Significa eso que comInstance debe ser administrado, y el patrón de disolves:

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

~MyClass() 
{ 
    DisposeComInstance(); 
} 

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

EDIT:

  1. Para evitar llenar mi clase con el patrón completo, podría simplemente sellar mi clase?
  2. ¿Cómo sé que ComClassName (y en general cualquier clase) no está administrado?

Respuesta

2

Parece que casi lo tienes clavado. Me gustaría volver al patrón en el que tienes una Disposición virtual protegida que toma un parámetro booleano que indica si los elementos gestionados deben desecharse. De esta forma, alguien que venga detrás de usted continuará al 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-comentario: asegúrese de que su Dispose (bool) puede tolerar que lo llamen muchas veces seguidas. También trate de minimizar la posibilidad de que se produzca una excepción (no siempre es posible). – user7116

+1

Comentario de meta-meta: además de eso, es importante que nunca adquiera cerraduras ni use el bloqueo durante su limpieza no administrada. – womp

3

En última instancia desea que este tipo de patrón:

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); 
} 

Para un gran artículo sobre por qué, echa un vistazo a Implementing IDisposable and the Dispose pattern properly.

+0

-1, debe mover los objetos no administrados fuera del bloque if (disponer). No administrado siempre debe ser limpiado. – user7116

+0

Gah! Tienes razón, lo copié de parte de mi código que usaba componentes con código no administrado pero implementado, y editado en el ejemplo del OP ... pegar para perder. Fijo. – womp

+0

+1, implementado correctamente ;-D – user7116

3

En primer lugar, estoy de acuerdo con los que sugieren que tiene un finalizador como una copia de seguridad , pero trate de evitar que se lo llame llamando explícitamente a myClass.Dispose o mediante 'using'.

p. Ej.

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

o

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

Marshall.ReleaseComObject

Si está utilizando una gran cantidad de objetos COM, también sugieren que utilice Mashall.ReleaseComObject (comObj) para limpiar explícitamente referencias el RCW.

Por lo tanto, el código como el sugerido en otra parte:

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

se convertiría en:

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

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

    comInstance = null; 
} 

mientras se comprueba el valor de retorno de ReleaseComObject() no es estrictamente necesario, me gusta comprobar que se asegúrese de que las cosas se incrementen/disminuyan según lo esperado.

La regla de 2 puntos

Si decide utilizar esto, algo a tener en cuenta es que puede que tenga que ser reprogramado para liberar adecuadamente los objetos COM algo de código. Uno en particular es lo que yo llamo la regla de 2 puntos. Cualquier línea que use objetos COM que contengan 2 puntos necesita mucha atención. Por ejemplo,

var name = myComObject.Address.Name; 

En esta declaración obtener una referencia al objeto COM Dirección incrementar su cuenta de referencias RCW, pero que no tienen la oportunidad de llamar ReleaseComObject. Una mejor manera de hacerlo sería a descomponerlo (try..finally omitido para mayor claridad):

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

donde MyReleaseComObject es un método de utilidad envolver mi cheque de conteo y FinalReleaseComObject() desde arriba.

Cuestiones relacionadas