2009-09-13 13 views
20

No creo que esta pregunta haya sido hecha antes. Estoy un poco confundido sobre la mejor manera de implementar IDisposable en una clase sellada específicamente, una clase sellada que no hereda de una clase base. (Es decir, una "clase sellada pura" que es mi término inventado.)Implementación IDisposable en una clase sellada

Quizás algunos de ustedes estén de acuerdo conmigo en que las pautas para implementar IDisposable son muy confusas. Dicho esto, quiero saber que la forma en que pretendo implementar IDisposable es suficiente y segura.

Estoy haciendo un código P/Invoke que asigna un IntPtr a través de Marshal.AllocHGlobal y, naturalmente, quiero deshacerme limpiamente de la memoria no administrada que he creado. Así que estoy pensando en algo como esto

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

Estoy asumiendo que ya MemBlock y está completamente cerrado y nunca se deriva de otra clase que la implementación de un virtual protected Dispose(bool disposing) no es necesario.

Además, ¿el finalizador es estrictamente necesario? Todos los pensamientos bienvenidos.

Respuesta

13

El finalizador es necesario como un mecanismo de reserva para eventualmente liberar recursos no administrados si se olvidó de llamar al Dispose.

No, no debe declarar un método virtual en una clase sealed. No compilaría en absoluto. Además, no se recomienda declarar nuevos miembros protected en las clases sealed.

+0

Pero, por supuesto, en el caso de que una clase sellada provenga de una clase base, entonces un Disposición virtual sería necesaria, ¿correcto? – zebrabox

+0

También. El finalizador significa agregar a una cola de finalizador y tener la sobrecarga de efectivamente una doble recolección de basura. Parece una gran penalización pagar por usar recursos no administrados. ¿No hay forma de evitar el golpe de rendimiento? – zebrabox

+0

En ese caso, 'anulará' el método. No puede declarar ningún método en una clase 'sealed' como' virtual'. Es un compilador ** error **. –

7

Una pequeña adición; en el caso general, un patrón común es tener un método Dispose(bool disposing), para saber si está en Dispose (donde hay más cosas disponibles) frente al finalizador (donde no debe tocar realmente ningún otro objeto gestionado conectado) .

Por ejemplo:

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í, es un buen punto Marc, pero si supiera que solo dispongo de recursos no administrados, ¿es estrictamente necesario? – zebrabox

+0

Además, es una pregunta muy estúpida, pero si el patrón Dispose es para liberar de manera determinista los recursos no administrados, ¿por qué querría gestionar los recursos gestionados desechables cuando el GC los limpie? – zebrabox

+0

Si está siendo determinista, querrá limpiar todo lo que está * encapsulando *; especialmente si ellos mismos son 'IDisposables'. Usted * no * haría esto en el finalizador, ya que es posible que ya se hayan recopilado (y: ya no es su trabajo). Y tienes razón; en este caso, aparte del 'SuppressFinalize' (que no importa mucho) no estamos haciendo nada, así que estaría bien no molestarnos; por eso enfaticé el caso * general *. –

7

De Joe Duffy's Weblog:

Para las clases selladas, este patrón tiene por qué no ser seguido, lo que significa que debe simplemente poner en práctica su finalizador y Desechar con los métodos simples (es decir, ~ T() (Finalizar) y Eliminar() en C#). Al elegir la última ruta, su código aún debe cumplir con las pautas siguientes con respecto a la implementación de la finalización de y la lógica de disposición .

Así que sí, deberías ser bueno.

Necesita el finalizador como mencionó Mehrdad. Si desea evitarlo, puede echar un vistazo al SafeHandle. No tengo suficiente experiencia con P/Invoke para sugerir el uso correcto.

+0

Gracias TrueWill! Miré a SafeHandle y, según Eric Lippert, el equipo de BCL lo consideró uno de los beneficios más importantes que presentaron en 'Whidbey' (lo siento, por ahora no puedo encontrar el enlace). Desafortunadamente, es una clase abstracta, por lo que debe aplicar la suya propia para cada situación, que es un poco asquerosa. – zebrabox

+1

@zebrabox: si bien es posible que deba ejecutar las suyas propias en algunas situaciones, la documentación establece: "Se proporciona un conjunto de clases preescritas derivadas de SafeHandle derivaciones abstractas, y este conjunto se encuentra en el espacio de nombres Microsoft.Win32.SafeHandles ". – TrueWill

+1

@TrueWill. Sí, muy cierto, pero solo para cosas como File Handles, Wait Handles, Pipe Handles y un montón de criptas. ¡Aún mejor que nada! – zebrabox

1

No puede declarar métodos virtuales en una clase sellada. También declarar miembros protegidos en una clase sellada le da una advertencia del compilador. Entonces lo has implementado correctamente. Llamar a GC.SuppressFinalize (esto) desde el finalizador no es necesario por razones obvias, pero no puede dañar.

Tener un finalizador es esencial cuando se trata de recursos no administrados, ya que no se liberan automáticamente, tiene que hacerlo en el finalizador y se llama automáticamente después de que el objeto se haya recolectado.

Cuestiones relacionadas