Pregunta

Tengo una clase de C# con un Dispose funcionar a través de IDisposable.Está destinado a ser utilizado dentro de un using bloquear para que el costoso recurso que maneja pueda liberarse de inmediato.

El problema es que ocurrió un error cuando se lanzó una excepción antes Dispose fue llamado, y el programador se olvidó de utilizar using o finally.

En C++, nunca tuve que preocuparme por esto.La llamada al destructor de una clase se insertaría automáticamente al final del alcance del objeto.La única forma de evitar que eso suceda sería usar el nuevo operador y sostener el objeto detrás de un puntero, pero eso requirió trabajo adicional para el programador no es algo que harían por accidente, como olvidarse de usar using.

¿Hay alguna manera de using ¿Bloque que se utilizará automáticamente en C#?

Muchas gracias.

ACTUALIZAR:

Me gustaría explicar por qué no acepto las respuestas del finalizador.Esas respuestas son técnicamente correctas en sí mismas, pero no son destructores de estilo C++.

Aquí está el error que encontré, reducido a lo esencial...

try
{
    PleaseDisposeMe a = new PleaseDisposeMe();
    throw new Exception();
    a.Dispose();
}
catch (Exception ex)
{
    Log(ex);
}

// This next call will throw a time-out exception unless the GC
// runs a.Dispose in time.
PleaseDisposeMe b = new PleaseDisposeMe();

Usando FXCop es una excelente sugerencia, pero si esa es mi única respuesta, mi pregunta tendría que convertirse en una súplica a la gente de C#, o usar C++.¿Alguien quiere veinte declaraciones de uso anidadas?

¿Fue útil?

Solución

Lamentablemente, no hay ninguna forma de hacer esto directamente en el código.Si este es un problema interno, existen varias soluciones de análisis de código que podrían detectar este tipo de problemas.¿Has investigado FxCop?Creo que esto detectará estas situaciones y en todos los casos en los que los objetos IDisposable puedan quedar colgados.Si es un componente que la gente usa fuera de su organización y no puede requerir FxCop, entonces la documentación es realmente su único recurso :).

Editar:En el caso de los finalizadores, esto realmente no garantiza cuándo se producirá la finalización.Entonces esta puede ser una solución para usted, pero depende de la situación.

Otros consejos

Donde trabajo utilizamos las siguientes pautas:

  • Cada clase IDisposable debe tener un finalizador
  • Siempre que se utilice un objeto IDisposable, se debe utilizar dentro de un bloque "usando".La única excepción es si el objeto es miembro de otra clase, en cuyo caso la clase contenedora debe ser IDisposable y debe llamar al método 'Dispose' del miembro en su propia implementación de 'Dispose'.Esto significa que el desarrollador nunca debe llamar a 'Dispose' excepto dentro de otro método 'Dispose', eliminando el error descrito en la pregunta.
  • El código en cada Finalizador debe comenzar con un registro de advertencia/error que nos notifique que se ha llamado al finalizador.De esta manera, tiene muchas posibilidades de detectar errores como los descritos anteriormente antes de publicar el código, además podría ser una pista de errores que ocurren en su sistema.

Para hacernos la vida más fácil, también tenemos un método SafeDispose en nuestra infraestructura, que llama al método Dispose de su argumento dentro de un bloque try-catch (con registro de errores), por si acaso (aunque se supone que los métodos Dispose no deben generar excepciones). ).

Ver también: Chris Lyonsugerencias de IDisposable

Editar:@Peleón:Una cosa que debes hacer es llamar a GC.SuppressFinalize dentro de 'Dispose', de modo que si el objeto fue eliminado, no sea "redispuesto".

También suele ser aconsejable llevar una bandera que indique si el objeto ya ha sido eliminado o no.El siguiente patrón suele ser bastante bueno:

class MyDisposable: IDisposable {
    public void Dispose() {
        lock(this) {
            if (disposed) {
                return;
            }

            disposed = true;
        }

        GC.SuppressFinalize(this);

        // Do actual disposing here ...
    }

    private bool disposed = false;
}

Por supuesto, el bloqueo no siempre es necesario, pero si no está seguro de si su clase se utilizará en un entorno multiproceso o no, es recomendable conservarlo.

@Peleón

Se llamará a If cuando el objeto se mueva fuera del alcance y el recolector de basura lo ordene.

Esta afirmación es engañosa y la leí incorrectamente:No hay absolutamente ninguna garantía de cuándo se llamará al finalizador.Tiene toda la razón en que billpg debería implementar un finalizador;sin embargo, no se llamará automáticamente cuando el objeto salga del alcance como él desea. Evidencia, el primer punto debajo Las operaciones de finalización tienen las siguientes limitaciones.

De hecho, Microsoft le dio una subvención a Chris Sells para crear una implementación de .NET que utilizara el recuento de referencias en lugar de la recolección de basura. Enlace.Al final resultó que había un considerable impacto en el rendimiento.

~ClassName()
{
}

EDITAR (negrita):

Se llamará si el objeto se mueve fuera del alcance y el recolector de basura lo ordena. sin embargo, esto no es determinista y no se garantiza que suceda en un momento determinado..Esto se llama Finalizador.Todos los objetos con un finalizador son colocados en una cola de finalización especial por el recolector de basura donde se invoca el método de finalización (por lo que técnicamente es un impacto en el rendimiento declarar finalizadores vacíos).

El patrón de eliminación "aceptado" según las Directrices marco es el siguiente con recursos no administrados:

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

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // tidy managed resources
            }

            // tidy unmanaged resources
        }
    }

Entonces, lo anterior significa que si alguien llama a Dispose, los recursos no administrados se ordenan.Sin embargo, en el caso de que alguien se olvide de llamar a Dispose o de una excepción que impida que se llame a Dispose, los recursos no administrados aún se ordenarán, solo un poco más tarde, cuando el GC se ponga manos a la obra (lo que incluye que la aplicación se cierre o finalice inesperadamente). ).

La mejor práctica es utilizar un finalizador en su clase y utilizar siempre using bloques.

En realidad, no existe un equivalente directo; los finalizadores parecen destructores de C, pero se comportan de manera diferente.

Se supone que debes anidar using bloques, es por eso que el diseño del código C# por defecto los coloca en la misma línea...

using (SqlConnection con = new SqlConnection("DB con str") )
using (SqlCommand com = new SqlCommand( con, "sql query") )
{
    //now code is indented one level
    //technically we're nested twice
}

Cuando no estás usando using puedes hacer lo que hace debajo del capó de todos modos:

PleaseDisposeMe a;
try
{
    a = new PleaseDisposeMe();
    throw new Exception();
}
catch (Exception ex) { Log(ex); }  
finally {    
    //this always executes, even with the exception
    a.Dispose(); 
}

Con código administrado, C# es muy bueno cuidando su propia memoria, incluso cuando las cosas no están bien eliminadas.Si se trata mucho de recursos no administrados, no es tan fuerte.

Esto no es diferente de que un programador se olvide de usar borrar en C++, excepto que al menos aquí el recolector de basura eventualmente lo alcanzará.

Y nunca necesitarás usar IDisposable si el único recurso que te preocupa es la memoria.El marco se encargará de eso por sí solo.IDisposable es solo para recursos no administrados como conexiones de bases de datos, flujos de archivos, sockets y similares.

Un mejor diseño es hacer que esta clase libere el costoso recurso por sí sola, antes de desecharlo.

Por ejemplo, si se trata de una conexión de base de datos, conéctese solo cuando sea necesario y libérela inmediatamente, mucho antes de que se elimine la clase real.

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