2009-09-22 11 views
13

Esta es una pregunta bastante básica, sin embargo, todavía estoy luchando con eso un poco.¿Debería instalarse IDisposable en cascada?

IDisposable se implementa cuando se desea habilitar al usuario de un objeto para liberar recursos subyacentes (por ejemplo, sockets, etc.) antes de que el objeto finalmente se recolecte como basura.

Cuando tengo una clase que contiene un DbConnection (implementa IDisposable), ¿mi clase necesita implementar IDisposable también y encadenar la llamada a DbConnection o cualquier otro objeto IDisposable que posea? De lo contrario, los recursos de DbConnections solo se liberarán cuando mi clase sea GarbageCollected, por lo que se eliminará la referencia a la conexión y GC finalizará la DbConnection.

Respuesta

9

Sí, usted ALWAYS implemente IDisposable si controla objetos desechables. SIEMPRE. Su código no se romperá si no lo hace, pero frustra el propósito de tener objetos desechables si no lo hace.

La regla general para la optimización de GC es:

  • Cualquier clase que controla los objetos no gestionados por la GC debe implementar un finalizador (y generalmente deben aplicar IDisposable también). De ahí provienen generalmente las clases desechables de "nivel superior": por lo general, controlan una MANIJA a una ventana, socket, mutex o lo que sea.
  • Cualquier clase que crea una instancia de un miembro IDisposable debe implementar IDisposable en sí misma, y ​​desechar() adecuadamente sus componentes.
  • Cualquier función que ejemplifique un objeto IDdisponible debería desechar() adecuadamente cuando se utiliza. No dejes que simplemente se salga del alcance.

Estas reglas se pueden doblar o ignorar si está escribiendo una aplicación para usted, pero cuando distribuya el código a otras personas, debe ser profesional y cumplir las reglas.

La lógica aquí es que cuando se controla la memoria fuera de la vista del GC, el motor del GC no puede gestionar adecuadamente el uso de la memoria. En su pila .NET, por ejemplo, puede tener un puntero de 4 bytes, pero en un terreno no administrado podría tener apuntados 200 MB de memoria. El motor de GC no intentará recopilar estos hasta que tenga varias docenas, porque todo lo que ve son unos pocos bytes; mientras que en el mundo real se parece mucho a una fuga de memoria.

Por lo tanto, la regla es que la memoria no administrada debe liberarse inmediatamente cuando termine de usarla (la cadena IDisposable hace esto por usted), mientras que la memoria administrada se libera por el motor del GC cada vez que lo hace.

+1

¿No es curioso entonces que DataSet.Dispose() no se encargue de Dispose() sus DataTables? – Nariman

+1

Esto realmente debería manejarse mucho mejor con el lenguaje: tener que encadenar IDisposable/Dispose a lo largo de una jerarquía de clases es absurdo y tedioso. – nicodemus13

6

Sí, su clase necesita ser IDisposable si necesita deshacerse de los objetos que utiliza. Un ejemplo de esto es StreamReader. Implementa IDisposable para que pueda deshacerse de su objeto de flujo asociado.

+0

pregunta es si _se_ necesita eliminar cualquier objeto. Podria_. ¿Cómo piensas sobre el ejemplo de DbConnection? –

+0

@Johannes: Un ejemplo de esto es tu clase. Debería implementar IDisposable para que pueda deshacerse de su objeto asociado DbConnnection. – Powerlord

+0

(Sí, intencionalmente parafraseé lo que dijo David) – Powerlord

0

Sin duda es una buena práctica hacerlo, especialmente cuando se trabaja con objetos pesados ​​/ no administrados.

Editar: Las mejores prácticas, pero no son obligatorias.

3

Si entiendo su pregunta correctamente, tiene una clase que usa una conexión Db. Desea asegurarse de que DbConnection esté correctamente eliminado cuando termine de trabajar con él o cuando se elimine su clase. Hay un par de formas de lograr esto.

Si está utilizando una conexión de base de datos como variable local en un método, puede aprovechar la instrucción using() {}.

using (SqlConnection sqlConnection = new SqlConnection(connStr))
{
...do stuff with connection here
}

La instrucción using() {} automáticamente llama a Dispose() en objetos declarados dentro de la(). (También requiere que los objetos declarados en() implementen IDisposable para asegurar que puedan ser desechados)

Si en cambio está trabajando con una DbConnection como una variable privada que se inicializa durante la construcción del objeto o algún otro método de inicialización, entonces probablemente Desea implementar IDisposable usted mismo y luego llame a _dbConnection.Dispose() en su método Dispose(). De esta forma, cuando se elimine su objeto, el objeto de conexión db también lo eliminará.

public class MyDALObj : IDisposable
{

public MyDalObj()
{
... create _dbConn object ...
}

public void Dispose()
{
_dbConn.Dispose();
}

private DbConnection _dbConn;
}

3

usted debe hacer esto, ya que es la única manera de que el usuario de su clase para asegurarse de que el recurso celebrada internamente es adecuadamente dispuesto.

Sin embargo, el patrón utilizado para Dispose() puede ser ligeramente diferente de lo que comúnmente se escribe, en este caso, ya que no tiene que diferenciar entre recursos administrados y no administrados (su recurso encapsulado siempre se trata como " "recurso" gestionado

Escribí una publicación de blog detallada sobre este tema específico - Encapsulating IDisposable Resources.

0

Como nunca se sabe cuando un objeto va a ser recolectado por GC, utilizamos la interfaz IDisposable para tener la oportunidad de liberar intencionalmente recursos no administrados antes de que un objeto sea recolectado. Si un objeto desechable no se elimina antes de ser recolectado, es posible que sus recursos no se liberen hasta que se cierre el AppDomain. Es casi una regla no escrita que cada objeto que tenga una referencia a un objeto IDisposable debe ser IDisposable y llamar al método Dispose de sus referencias IDisposable en su propio método Dispose.

0

Por supuesto, puede eliminar una gran parte del costo de (re) implementación de IDisposable y obtener algo muy cercano a la finalización determinística de objetos en el montón administrado si usa C++/CLI. Este es un aspecto a menudo (me parece) desapercibido de un lenguaje que mucha gente parece consignar al bin "solo para el código de pegamento".

+0

No estoy familiarizado con C++/CLI, pero parece un lenguaje interesante. ¿Maneja algunos de los casos problemáticos para los cuales vb.net y C# no brindan soluciones razonables (por ejemplo, limpiar un objeto parcialmente construido cuando se lanza una excepción desde un constructor de clase derivada?) En caso afirmativo, ¿dicho manejo se limita a los casos en que la base y la clase derivada están escritas en C++/CLI, o también puede proporcionar dicha protección en los casos en que una u otra (o clases base y sub derivadas) están escritas? en otros idiomas? – supercat

0

Cuando proporciona control explícito utilizando Dispose, debe proporcionar una limpieza implícita con el método Finalize. Finalize proporciona una copia de seguridad para evitar que los recursos se filtren permanentemente si el programador no puede llamar a Dispose.

Creo que la mejor manera de implementar esto es usar la combinación de método Dispose y Finalize. Puede encontrar más Here.

+1

Los finalizadores te dan una última oportunidad para liberar tus PROPIOS recursos no administrados si no se invocó el método Dispose(). Solo implementa un finalizador si tiene recursos no administrados para limpiar. El finalizador no llama a los métodos Dispose() de los objetos miembros. Más bien, su método de eliminación debe llamar a su finalizador en su lugar (y luego suprimir la finalización). – tylerl

+0

exactamente, porque no se garantiza que las referencias a otros objetos sean válidas en el finalizador. Podrían haber sido recogidos antes. –

2

Hay dos escenarios distintos:

  1. Su objeto es dado una referencia de objeto a utilizar, ya sea a través de un argumento del constructor o una propiedad, y este objeto implementa IDisposable.
  2. Su objeto construye una instancia de un objeto que implementa IDisposable.

En el segundo caso, su objeto es responsable de los recursos involucrados, por lo que su objeto debe implementar IDisposable, y cuando se deseche, debe deshacerse del objeto que construyó.

Su DbConnection corresponde a este segundo caso, entonces sí, su objeto debería implementar IDisposable, y luego desechar la conexión.

En el primer caso, debe decidir en los siguientes tres soluciones:

  1. Su objeto sólo hace referencia a un objeto externo. Su objeto no debe deshacerse de este objeto externo. No necesita implementar IDisposable para este caso (es decir, para este objeto específico, si también internamente construye un objeto desechable, vuelve al segundo caso anterior).
  2. Su objeto se hace responsable del objeto externo. En este caso, vuelves al segundo caso, aunque tu objeto no fue el que construyó este objeto externo. Aquí implementa IDisposable y se deshace del objeto que le asignan.
  3. Implementa una forma para que el mundo exterior le diga cuál de las dos primeras soluciones debe elegir. Por ejemplo, un constructor puede tener una conexión, y un argumento booleano (o idealmente un valor enum) que le dice al constructor si su objeto ahora posee la conexión suministrada. Aquí también debe implementar IDisposable, pero en el método Dispose, debe verificar la propiedad y solo deshacerse de la conexión suministrada si es su propietario.

Eso fue una gran cantidad de texto, así que vamos a resumir:

  1. objetos de su propiedad, tiene que desechar.
  2. Objetos que no tiene, no los desecha.

También hay un tercer caso, que no parece que tenga, pero no obstante.

En el caso en el que la construcción, uso y descarte, un objeto localmente, dentro de un único método, sin pasar alrededor o almacenarlo en los campos de la clase, se utiliza la instrucción using lugar, como este:

using (IDbConnection conn = ....()) 
{ 
} 
Cuestiones relacionadas