2010-07-06 14 views
11

estoy trabajando en una clase que se ocupa de una gran cantidad de objetos de SQL - Conexión, Comando, adaptador de datos, CommandBuilder, etc Hay varias instancias en las que tenemos un código como éste:función genérica para manejar desechar objetos IDisposable

if(command != null) 
{ 
    command.Dispose(); 
} 

if(dataAdapter != null) 
{ 
    dataAdapter.Dispose(); 
} 

etc 

Sé que esto es bastante insuficiente en términos de duplicación, pero ha comenzado a oler. La razón por la que creo que huele es porque en algunos casos el objeto también se establece en nulo.

if(command != null) 
{ 
    command.Dispose(); 
    command = null; 
} 

Me gustaría deshacerme de la duplicación si es posible. He encontrado este método genérico para deshacerse de un objeto y establecerlo como nulo.

private void DisposeObject<TDisposable>(ref TDisposable disposableObject) 
    where TDisposable : class, IDisposable 
{ 
    if(disposableObject != null) 
    { 
     disposableObject.Dispose(); 
     disposableObject = null; 
    } 
} 

Mis preguntas son ...

  1. ¿Es esta función genérica una mala idea?
  2. ¿Es necesario establecer el objeto en null?

EDIT:

Soy consciente de la declaración using, sin embargo, no siempre se puede usar porque tengo algunas variables miembro que deben persistir durante más tiempo de una llamada. Por ejemplo, los objetos de conexión y transacción.

Gracias!

Respuesta

1

que asumen estos son campos y variables no locales, de ahí la palabra clave using no tiene sentido.

Is this generic function a bad idea?

Creo que es una buena idea, y he usado una función similar varias veces; +1 para hacerlo genérico.

Is it necessary to set the object to null?

Técnicamente un objeto debe permitir que múltiples llamadas a su método Dispose. (Por ejemplo, esto sucede si un objeto se resucita durante la finalización). En la práctica, depende de usted si confía en los autores de estas clases o si desea codificar a la defensiva. Personalmente, verifico nulo, luego establezco referencias a null luego.

Editar: Si este código se encuentra dentro de su propio método objeto Dispose entonces no establecer referencias a nula no perder memoria. En cambio, es útil como una defensa contra la eliminación doble.

6

Debe implementar IDisposable en la clase que posee estos campos. Ver my blog post sobre el tema. Si esto no funciona bien, la clase no sigue los principios de OOP, y necesita ser refactorizada.

Es not necessary para establecer las variables a null después de eliminarlas.

+0

No puedo usar la instrucción 'using' para la mayoría de mis casos. He actualizado mi pregunta. –

+0

@Jerod: He actualizado mi respuesta para que coincida. –

7

Debería considerar si puede usar la declaración using.

using (SqlCommand command = ...) 
{ 
    // ... 
} 

Esto asegura que Dispose se llama en el objeto de comando cuando el control deja el alcance del uso. Esto tiene una serie de ventajas sobre la escritura del código de limpieza usted mismo como lo ha hecho:

  • Es más conciso.
  • La variable nunca se establecerá como nula; se declara e inicializa en una declaración.
  • La variable queda fuera del alcance cuando se elimina el objeto por lo que se reduce el riesgo de intentar accidentalmente acceder a un recurso eliminado.
  • Es una excepción segura.
  • Si anida las instrucciones de uso, los recursos se eliminan naturalmente en el orden correcto (en el orden inverso en que se crearon).

Is it necessary to set the object to null?

No suele ser necesaria para establecer las variables a cero cuando haya terminado de usarlos. Lo importante es que llame a Dispose cuando haya terminado de usar el recurso.Si utiliza el patrón anterior, que no sólo es innecesario establecer la variable en null - le dará un error de compilación:

 
Cannot assign to 'c' because it is a 'using variable' 

Una cosa a destacar es que using sólo funciona si un objeto se adquiere y se dispone en la misma llamada de método. No puede usar este patrón si sus recursos necesitan permanecer con vida durante la duración de más de una llamada a método. En este caso, es posible que desee que su clase implemente IDisposable y asegúrese de que los recursos se limpien cuando se llame al método Dispose. En este caso necesitarás un código como el que has escrito. Establecer variables en null no es incorrecto en este caso, pero no es importante porque el recolector de basura limpiará la memoria correctamente de todos modos. Lo importante es asegurarse de que todos los recursos que posee se eliminen cuando se llame a su método de disposición, y lo está haciendo.

Un par de detalles de implementación:

  • Debe asegurarse de que si su Desechar se llama dos veces que ni una excepción. Su función de utilidad maneja este caso correctamente.
  • debe asegurarse de que los métodos pertinentes sobre el objeto plantean una ObjectDisposedException si su objeto ya se ha eliminado.
+1

Esto, por supuesto, asume que la vida útil del recurso es solo una llamada al método, lo que es poco probable en este caso, como lo indica la necesidad de verificar primero los varaibles como nulos. –

+0

@James, tienes toda la razón. @ Mark, he actualizado mi pregunta. –

0

Otros han recomendado la construcción using, que también recomiendo. Sin embargo, me gustaría señalar que, incluso si necesita un método de utilidad, es completamente innecesario hacerlo genérico de la manera que lo ha hecho. Simplemente declarar su método para tomar una IDisposable:

private static void DisposeObject(ref IDisposable disposableObject) 
{ 
    if(disposableObject != null) 
    { 
     disposableObject.Dispose(); 
     disposableObject = null; 
    } 
} 
+0

Intenté esto, pero por alguna razón tuve que realizar todas mis llamadas al método. Estoy de acuerdo en que esto debería funcionar! Tendré que verificarlo dos veces. –

+0

@Jerod: el embalaje no funcionará, ya que está cambiando el valor de 'desechable' en lugar de' mTransaction'. @JS: Dado que vamos a estar * modificando * disposableObject, el compilador necesita saber el tipo exacto que es. –

+2

@Jerod - los parámetros del método no pueden ser covariantes en este caso debido a la palabra clave 'ref', de lo contrario, por ejemplo, podría pasar un' SqlConnection', luego establecer el objeto a una instancia de 'FileStream' dentro del método (lo que violaría la seguridad del tipo del argumento) –

1

Voy a suponer que está creando el recurso en un método, eliminándolo en otro y usándolo en uno o más, haciendo que la declaración using sea inútil para usted.

En cuyo caso, el método es perfectamente bien.

En cuanto a la segunda parte de su pregunta ("¿El ajuste es necesario para anular?"), La respuesta simple es "No, pero no hace daño a nada".

La mayoría de los objetos contienen un recurso: memoria, que la recolección de basura trata con la liberación, por lo que no tenemos que preocuparnos por ello. Algunos tienen también algún otro recurso: un identificador de archivo, una conexión de base de datos, etc. Para la segunda categoría, debemos implementar IDisposable, para liberar ese otro recurso.

Una vez que se llama al método Dispose, ambas categorías son iguales: están reteniendo la memoria. En este caso, podemos simplemente dejar que la variable salga del alcance, dejando caer la referencia a la memoria y permitiendo que GC la libere finalmente. O podemos forzar el problema, estableciendo la variable como nula y dejando caer explícitamente la variable. referencia a la memoria. Todavía tenemos que esperar hasta que el GC entre en acción para que la memoria se libere realmente, y más que probable que la variable salga del alcance de todos modos, momentos después para establecerla como nula, por lo que en la gran mayoría de los casos, tendrá no tiene ningún efecto, pero en unos pocos casos, permitirá que la memoria se libere unos segundos antes.

Sin embargo, si su caso específico, en el que está mirando para nula para ver si debe llamar a Dispose en absoluto, es probable que debe establecen a NULL, si hay una posibilidad de que pudiera llamar a Dispose() dos veces.

0

Nunca necesita establecer variables en null. El objetivo de IDisposable.Dispose es poner el objeto en un estado en el que pueda colgar inofensivamente en la memoria hasta que el GC lo finalice, por lo que simplemente "deseche y olvide".

Tengo curiosidad sobre por qué cree que no puede usar la instrucción using. Crear un objeto en un método y eliminarlo en un método diferente es realmente olor a código grande en mi libro, porque pronto perderá la pista de lo que está abierto en el lugar. Es mejor refactorizar el código como el siguiente:

using(var xxx = whatever()) { 
    LotsOfProcessing(xxx); 
    EvenMoreProcessing(xxx); 
    NowUseItAgain(xxx); 
} 

Estoy seguro de que es un nombre de patrón estándar para esto, pero acabo de llamarlo "destruir todo lo que se crea, pero nada más".

+0

Estoy de acuerdo en que esto no es sin códigos huele. Este código proviene de una clase de biblioteca SQL que se utiliza en varios productos. La razón más grande por la que la instrucción 'using' no funciona es porque una de las características necesarias es el soporte de transacciones. El usuario de la biblioteca nos dice que iniciemos la transacción, luego pueden hacer múltiples manipulaciones de datos y luego nos dicen que confirmemos la transacción. Creo que es un caso de uso muy razonable, ya que crea una separación entre el código del cliente y el código específico de SQL. –

+0

@Jerod, ¿no es posible crear la conexión, comenzar la transacción, llamar a todos los métodos de la biblioteca, confirmar la transacción y disponer de la conexión, todo en un solo método? Lo que describes es exactamente cómo diseñé mis clases de biblioteca de SQL, pero aún puedo usar sentencias 'using'. –

+0

es posible, excepto que el usuario necesitaría conocer los detalles intrincados de nuestra biblioteca. Desafortunadamente, no estoy en libertad de cambiar nuestra API pública en este momento y tengo que trabajar con lo que obtuve. –

3

Si los objetos tienen que realizar una gran cantidad de tareas de limpieza, es posible que deseen realizar un seguimiento de lo que se debe eliminar en una lista separada de elementos desechables y manejarlos todos a la vez. Luego, en el desmontaje, no es necesario que recuerde todo lo que necesita eliminarse (ni tiene que comprobar si es nulo, simplemente se ve en la lista).

Esto probablemente no se compila, pero para fines de expansión, podría incluir un RecycleBin en su clase. Entonces la clase solo necesita deshacerse del contenedor.

public class RecycleBin : IDisposable 
{ 
    private List<IDisposable> _toDispose = new List<IDisposable>(); 

    public void RememberToDispose(IDisposable disposable) 
    { 
     _toDispose.Add(disposable); 
    } 

    public void Dispose() 
    { 
     foreach(var d in _toDispose) 
      d.Dispose(); 

     _toDispose.Clear(); 
    } 
} 
+0

+1 Esto no funciona para mi situación actual porque no me estoy deshaciendo de todo de una vez. Aunque me gusta esta sugerencia y podría tener que usarla en el futuro. –

1

Dado que IDisposable no incluye ninguna forma estándar de la determinación de si un objeto ha sido dispuesto, me gusta poner las cosas a nulo cuando disponer de ellos. Para estar seguro, deshacerse de un objeto que ya ha sido eliminado es inofensivo, pero es bueno poder examinar un objeto en una ventana de observación y decir de un vistazo qué campos han sido eliminados. También es bueno tener la prueba de código para garantizar que los objetos que deberían haberse eliminado son (suponiendo que el código se adhiera a la convención de anular las variables al deshacerse de ellos, y en ningún otro momento).