2009-11-09 13 views
7

tengo una clase abstracta que implementa IDisposable, así:C# Desechar método abstracto

public abstract class ConnectionAccessor : IDisposable 
{ 
    public abstract void Dispose(); 
} 

En Visual Studio Team System 2008, me encontré con el análisis de código en mi proyecto y una de las advertencias que se le ocurrió fue el siguiente:

Microsoft.Design: Modificar 'ConnectionAccessor.Dispose()' para que llame a Dispose (true), luego llama GC.SuppressFinalize en la instancia del objeto actual ('esto' o 'Yo' en Visual Basic), y luego regresa.

¿Es solo una tontería decirme que modifique el cuerpo de un método abstracto, o debo hacer algo más en cualquier instancia derivada de Dispose?

+0

¿Por qué necesita agregar Dispose() a su interfaz? Si se hereda de IDisposable, el método Dispose() ya es parte de su interfaz. –

+0

Seth, debe implementar todos los miembros de la interfaz. Salirlo no compilaría. –

Respuesta

12

que debe seguir el patrón convencional para implementar Dispose. Hacer Dispose() virtual se considera mala práctica, porque el patrón convencional enfatiza la reutilización del código en "limpieza administrada" (cliente API llamando al Dispose() directamente o por using) y "limpieza no gestionada" (finalizador de llamadas del GC). Para recordar, el patrón es la siguiente:

public class Base 
{ 
    ~Base() 
    { 
     Dispose(false); 
    } 

    public void Dispose() 
    { 
     Dispose(true); 
     GC.SuppressFinalize(this); // so that Dispose(false) isn't called later 
    } 

    protected virtual void Dispose(bool disposing) 
    { 
     if (disposing) 
     { 
      // Dispose all owned managed objects 
     } 

     // Release unmanaged resources 
    } 
} 

clave aquí es que no hay duplicación entre finalizador y Dispose para la limpieza no administrado, y sin embargo ninguna clase derivada puede extender tanto la limpieza administrado y no administrado.

Para su caso, lo que debe hacer es lo siguiente:

protected abstract void Dispose(bool disposing) 

y dejar todo lo demás como es. Incluso eso tiene un valor dudoso, ya que ahora aplica sus clases derivadas para implementar Dispose, y ¿cómo sabe que todas las necesitan? Si su clase base no tiene nada que desechar, pero es probable que la mayoría de las clases derivadas sí lo hagan (con algunas excepciones), simplemente proporcione una implementación vacía. Es lo que hace System.IO.Stream (sí mismo abstracto), entonces hay un precedente.

+0

Prefiero que las personas sean advertidas de hacer esto: http://nitoprograms.blogspot.com/2009/08/how-to-implement-disponible-y.html - el patrón sería muchísimo más simple si no tenía soporte integrado para las clases que mezclaban recursos administrados y no administrados. –

+0

Si cualquier clase derivada de una clase particular necesitará implementar IDisposable, y si un objeto que espera recibir un tipo de clase base puede terminar reteniendo la última referencia útil, la clase base debería implementar IDisposable, incluso si el la gran mayoría de las clases derivadas no lo necesitarán. – supercat

+0

¿Este patrón no está desactualizado desde .net 2? Según tengo entendido, los recursos no administrados se deben mantener mediante algún tipo de manejo seguro/crítico y las clases implementadas por el usuario normal ya no necesitan un finalizador. Y su 'Dispose' simplemente llama a' Dispose' en el mango. – CodesInChaos

10

La advertencia básicamente le dice que implemente el Dispose pattern en su clase.

El código resultante debe tener este aspecto:

public abstract class ConnectionAccessor : IDisposable 
{ 
    ~ConnectionAccessor() 
    { 
     Dispose(false); 
    } 

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

    protected virtual void Dispose(bool disposing) 
    { 
    } 
} 
+3

+1 por mencionar el patrón, y señalar que el Dispose (bool) debe ser el método virtual/abstracto que las clases derivadas deben implementar o anular, no Dispose(). – Yoopergeek

-1

Sin embargo, la advertencia es interesante. Eric Lippert, uno de los diseñadores de C#, blogueó sobre por qué los mensajes de error deberían ser "Diagnósticos pero no preceptivos: describa el problema, no la solución". Read here.

+1

No es un mensaje de error del compilador. Es el resultado de ejecutar una herramienta que específicamente señala cosas problemáticas. No tiene nada de malo señalar también la solución. –

+0

Claro. Solo quería compartir el enlace. Me vino a la mente porque el mensaje no expresa el problema sino que brinda la solución solamente. Afortunadamente había un mensaje anterior haciendo eso. –

+0

Esto es algo tangencial a la pregunta original. –

1

A pesar de que parece un poco como puntillosa, el consejo es válido. Ya está indicando que espera que cualquier subtipo de ConnectionAccessor tenga algo que deben eliminar. Por lo tanto, parece mejor asegurarse de que la clase base realice la limpieza adecuada (en términos de la llamada GC.SuppressFinalize) en lugar de confiar en cada subtipo para hacerlo.

uso el patrón de disponer mencionado en el libro de Bruce Wagner Effective C# que es básicamente:

public class BaseClass : IDisposable 
{ 
    private bool _disposed = false; 
    ~BaseClass() 
    { 
     Dispose(false); 
    } 

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

    protected virtual void Dispose(bool disposing) 
    { 
     if (_disposed) 
      return; 

     if (disposing) 
     { 
      //release managed resources 
     } 

     //release unmanaged resources 

     _disposed = true; 
    } 
} 

public void Derived : BaseClass 
{ 
    private bool _disposed = false; 

    protected override void Dispose(bool disposing) 
    { 
     if (_disposed) 
      return; 

     if (disposing) 
     { 
      //release managed resources 
     } 

     //release unmanaged resources 

     base.Dispose(disposing); 
     _disposed = true; 
    } 
3

La única queja que tendría con las respuestas proporcionadas hasta ahora es que todos ellos suponen que necesidad de tener un finalizador, que no es necesariamente el caso. Hay una sobrecarga de rendimiento bastante significativa asociada con la finalización, que no quisiera imponer a todas mis clases derivadas si no fuera necesario.

Consulte this blog post por Joe Duffy, que explica cuándo puede necesitar o no un finalizador, y cómo implementar correctamente el patrón Eliminar en cualquier caso.
Resumiendo la publicación del blog de Joe, a menos que esté haciendo algo de bajo nivel relacionado con la memoria no administrada, no debe implementar un finalizador. Como regla general, si su clase solo contiene referencias a tipos administrados que implementan IDisposable por sí mismos, no necesita el finalizador (pero debe implementar IDisposable y disponer de esos recursos). Si está asignando recursos no administrados directamente desde su código (PInvoke?) Y esos recursos deben ser liberados, necesita uno. Una clase derivada siempre puede agregar un finalizador si realmente lo necesita, pero forzar a todas las clases derivadas a tener un finalizador al ponerlo en la clase base provoca que todas las clases derivadas se vean afectadas por el golpe de rendimiento de los objetos finalizables cuando esa sobrecarga no puede ser necesario.

+0

Mi queja favorita. La mayoría de las personas simplemente no se molestan en tratar de entender esto, y el mejor consejo que tienden a obtener es simplemente implementar el patrón de Microsoft. Esto es lo que creo que es un consejo mucho mejor que el patrón "oficial": http://nitoprograms.blogspot.com/2009/08/how-to-implement-disposable-and.html –

+0

Esa es de hecho una buena publicación de blog en el sujeto - simple y también el punto. –

Cuestiones relacionadas