Pregunta

Tengo una clase abstracta que implementa IDisposable, así:

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

En 2008 Team System Visual Studio, me encontré con el análisis de código en mi proyecto y una de las advertencias que surgió 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 sólo haciendo el tonto, me dice que modificar el cuerpo de un método abstracto, o debería hacer algo más en cualquier instancia derivada de Dispose?

¿Fue útil?

Solución

Usted debe seguir el modelo convencional para la implementación de Dispose. Haciendo Dispose() virtuales se considera una mala práctica, porque el patrón convencional hace hincapié en la reutilización de código en la "limpieza administrada" (cliente API Dispose() llamando directamente o por medio de using) y "limpieza no administrado" (GC finalizador llamando). 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
    }
}

La 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.

En 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 es de dudoso valor, ya que están haciendo cumplir sus clases derivadas para implementar Dispose ahora - y ¿cómo se sabe que todos ellos lo necesitan? Si su clase base no tiene nada que desechar, pero las clases más probable derivados do (con unas pocas excepciones, tal vez), entonces simplemente proporcionar una implementación vacía. Es lo que System.IO.Stream (resumen propiamente dicho) lo hace, por lo que existe un precedente.

Otros consejos

La advertencia básicamente le dice a poner en práctica el Desechar el patrón 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)
    {
    }
}

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

esta entrada del blog Joe Duffy, lo que explica en las que puede o no puede necesitar un finalizador, y cómo implementar adecuadamente el patrón de botar en cualquier caso.
Resumiendo el blog de Joe, a menos que usted está haciendo algo bastante bajo nivel se trata de memoria no administrada, no se debe implementar un finalizador. Como regla general, si su clase sólo tiene referencias a tipos administrados que implementan IDisposable sí mismos, no se necesita el finalizador (pero debería implementar IDisposable y disponer de esos recursos). Si se están asignando recursos no administrados directamente desde el código (PInvoke?) Y se debe liberar esos recursos, lo necesita. Una clase derivada siempre se puede añadir un finalizador si realmente lo necesita, pero obligando a todas las clases derivadas de tener un finalizador poniéndolo en la clase base provoca que las clases de todos los derivados que se vean afectados por el impacto en el rendimiento de los objetos finalizables cuando esa sobrecarga puede no ser necesario.

A pesar de que parece un poco como puntillosa, el consejo es válido. Ya está indicando que esperar algún subtipos de ConnectionAccessor tendrán algo que tienen que desechar. Por lo tanto, parece mejor para asegurar que la limpieza adecuada se hace (en términos de la llamada GC.SuppressFinalize) por la clase base en lugar de confiar en cada subtipo de hacerlo.

Yo uso el patrón disponer mencionado en el libro de Bruce Wagner efectiva 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;
    }

La advertencia es interesante, sin embargo. Eric Lippert, uno de los diseñadores de C #, escribió en su blog acerca de por qué los mensajes de error deben ser "diagnóstico pero no prescriptiva: describir el problema, no la solución". Lea aquí .

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top