¿Debo disponer() de DataSet y DataTable?
Tanto DataSet como DataTable implementan IDisposable, por lo que, según las mejores prácticas convencionales, debería llamar a sus métodos Dispose().
Sin embargo, por lo que he leído hasta ahora, DataSet y DataTable en realidad no tienen recursos no administrados, por lo que Dispose() en realidad no hace mucho.
Además, no puedo usarlo simplemente using(DataSet myDataSet...)
porque DataSet tiene una colección de DataTables.
Entonces, para estar seguro, necesitaría iterar a través de myDataSet.Tables, deshacerme de cada una de las DataTables y luego deshacerme del DataSet.
Entonces, ¿vale la pena llamar a Dispose() en todos mis conjuntos de datos y tablas de datos?
Apéndice:
Para aquellos de ustedes que piensan que se debe eliminar DataSet: en general, el patrón para eliminar es usar using
o try..finally
, porque desea garantizar que se llamará a Dispose().
Sin embargo, esto se pone feo muy rápidamente para una colección. Por ejemplo, ¿qué haces si una de las llamadas a Dispose() genera una excepción? ¿Lo tragas (lo cual es "malo") para poder continuar desechando el siguiente elemento?
¿O sugiere que simplemente llame a myDataSet.Dispose() y me olvide de desechar las DataTables en myDataSet.Tables?
Aquí hay un par de discusiones que explican por qué Dispose no es necesario para un DataSet.
¿Desechar o no desechar? :
El método Dispose en DataSet existe SOLO debido al efecto secundario de la herencia; en otras palabras, en realidad no hace nada útil en la finalización.
¿Debería invocarse Dispose en objetos DataTable y DataSet? incluye alguna explicación de un MVP:
El espacio de nombres system.data (ADONET) no contiene recursos no administrados. Por lo tanto, no es necesario deshacerse de ninguno de ellos siempre que no le haya agregado algo especial.
¿Comprende el método Dispose y los conjuntos de datos? tiene un comentario de la autoridad Scott Allen:
En la práctica, rara vez desechamos un conjunto de datos porque ofrece pocos beneficios".
Entonces, el consenso es que actualmente no hay una buena razón para llamar a Dispose en un DataSet.
Actualización (1 de diciembre de 2009):
Me gustaría modificar esta respuesta y admitir que la respuesta original era errónea.
El análisis original se aplica a objetos que requieren finalización, y el punto de que las prácticas no deben aceptarse en la superficie sin una comprensión precisa y profunda sigue en pie.
Sin embargo, resulta que DataSets, DataViews y DataTables suprimen la finalización en sus constructores ; es por eso que llamar a Dispose() en ellos explícitamente no hace nada.
Presumiblemente, esto sucede porque no tienen recursos no administrados; entonces, a pesar de que MarshalByValueComponent tiene en cuenta los recursos no administrados, estas implementaciones particulares no tienen la necesidad y, por lo tanto, pueden renunciar a la finalización.
(El hecho de que los autores de .NET se encarguen de suprimir la finalización en los tipos que normalmente ocupan la mayor cantidad de memoria habla de la importancia de esta práctica en general para los tipos finalizables).
No obstante, es bastante sorprendente que estos detalles aún estén poco documentados desde el inicio de .NET Framework (hace casi 8 años) (que esencialmente te dejen a tu aire cribar material conflictivo y ambiguo para unir las piezas). a veces resulta frustrante, pero proporciona una comprensión más completa del marco en el que confiamos todos los días).
Después de mucha lectura, esto es lo que entiendo:
Si un objeto requiere finalización, podría ocupar memoria más tiempo del necesario. Este es el motivo: a) Cualquier tipo que defina un destructor (o herede de un tipo que defina un destructor) se considera finalizable; b) En el momento de la asignación (antes de que se ejecute el constructor), se coloca un puntero en la cola de finalización; c) Un objeto finalizable normalmente requiere que se recuperen 2 colecciones (en lugar de la estándar 1); d) Suprimir la finalización no elimina un objeto de la cola de finalización (según lo informado por !FinalizeQueue en SOS) Este comando es engañoso; Saber qué objetos están en la cola de finalización (en sí mismo) no es útil; Sería útil saber qué objetos están en la cola de finalización y aún requieren finalización (¿hay algún comando para esto?)
La supresión de la finalización se desactiva un poco en el encabezado del objeto, lo que indica al tiempo de ejecución que no necesita que se invoque su Finalizador ( no necesita mover la cola FReachable); Permanece en la cola de finalización (y !FinalizeQueue continúa informándolo en SOS)
Las clases DataTable, DataSet y DataView tienen su raíz en MarshalByValueComponent, un objeto finalizable que puede (potencialmente) manejar recursos no administrados.
- Debido a que DataTable, DataSet y DataView no introducen recursos no administrados, suprimen la finalización en sus constructores.
- Si bien este es un patrón inusual, libera a la persona que llama de tener que preocuparse por llamar a Dispose después de su uso.
- Esto, y el hecho de que las DataTables pueden potencialmente compartirse entre diferentes DataSets, es probablemente la razón por la que a los DataSets no les interesa deshacerse de las DataTables secundarias.
- Esto también significa que estos objetos aparecerán bajo !FinalizeQueue en SOS.
- Sin embargo, estos objetos aún deberían poder recuperarse después de una única colección, al igual que sus homólogos no finalizables.
4 (nuevas referencias):
- http://www.devnewsgroups.net/dotnetframework/t19821-finalize-queue-windbg-sos.aspx
- http://blogs.msdn.com/tom/archive/2008/04/28/asp-net-tips-looking-at-the-finalization-queue.aspx
- http://issuu.com/arifaat/docs/asp_net_3.5unleashed
- http://msdn.microsoft.com/en-us/magazine/bb985013.aspx
- http://blogs.msdn.com/tess/archive/2006/03/27/561715.aspx
Respuesta original:
Hay muchas respuestas engañosas y generalmente muy pobres sobre esto: cualquiera que haya llegado aquí debe ignorar el ruido y leer atentamente las referencias a continuación.
Sin duda, se debe invocar Dispose en cualquier objeto finalizable.
Las tablas de datos son finalizables.
Llamar a Dispose acelera significativamente la recuperación de memoria.
MarshalByValueComponent llama a GC.SuppressFinalize(this) en su Dispose(); omitir esto significa tener que esperar docenas, si no cientos, de colecciones Gen0 antes de recuperar la memoria:
Con este conocimiento básico de finalización ya podemos deducir algunas cosas muy importantes:
Primero, los objetos que necesitan finalización viven más que los objetos que no la necesitan. De hecho, pueden vivir mucho más. Por ejemplo, supongamos que es necesario finalizar un objeto que está en gen2. La finalización se programará, pero el objeto aún está en gen2, por lo que no se volverá a recolectar hasta que se realice la próxima recolección gen2. De hecho, eso podría ser mucho tiempo y, de hecho, si las cosas van bien, será mucho tiempo, porque las colecciones gen2 son costosas y, por lo tanto, queremos que sucedan con muy poca frecuencia. Es posible que los objetos más antiguos que necesitan finalización tengan que esperar docenas, si no cientos, de colecciones gen0 antes de recuperar su espacio.
En segundo lugar, los objetos que necesitan finalización causan daños colaterales. Dado que los punteros internos de los objetos deben seguir siendo válidos, no sólo los objetos que necesitan finalización directamente permanecerán en la memoria, sino que todo a lo que el objeto hace referencia, directa e indirectamente, también permanecerá en la memoria. Si un enorme árbol de objetos estuviera anclado por un único objeto que requiriera finalización, entonces todo el árbol permanecería, potencialmente durante mucho tiempo, como acabamos de comentar. Por lo tanto, es importante utilizar finalizadores con moderación y colocarlos en objetos que tengan la menor cantidad posible de punteros de objeto internos. En el ejemplo de árbol que acabo de dar, puedes evitar fácilmente el problema moviendo los recursos que necesitan finalización a un objeto separado y manteniendo una referencia a ese objeto en la raíz del árbol. Con ese modesto cambio, solo un objeto (con suerte, un objeto pequeño y bonito) permanecería y el costo de finalización se minimizaría.
Finalmente, los objetos que necesitan finalización crean trabajo para el subproceso finalizador. Si su proceso de finalización es complejo, el único subproceso finalizador dedicará mucho tiempo a realizar esos pasos, lo que puede provocar una acumulación de trabajo y, por lo tanto, hacer que más objetos permanezcan esperando la finalización. Por lo tanto, es de vital importancia que los finalizadores hagan el menor trabajo posible. Recuerde también que, aunque todos los punteros de objetos siguen siendo válidos durante la finalización, puede darse el caso de que esos punteros conduzcan a objetos que ya han sido finalizados y, por lo tanto, pueden ser menos útiles. Generalmente es más seguro evitar seguir los punteros de objetos en el código de finalización aunque los punteros sean válidos. Lo mejor es una ruta de código de finalización corta y segura.
Tómelo de alguien que ha visto cientos de MB de DataTables no referenciadas en Gen2: esto es enormemente importante y las respuestas de este hilo lo pasan por alto por completo.
Referencias:
1 : http://msdn.microsoft.com/en-us/library/ms973837.aspx
2 - http://vineetgupta.spaces.live.com/blog/cns!8DE4BDC896BEE1AD!1104.entry http://www.dotnetfunda.com/articles/article524-net-best-practice-no-2-improve-garbage -rendimiento-recolector-usando-finalizeddispose-pattern.aspx
3 - http://codeidol.com/csharp/net-framework/Inside-the-CLR/Automatic-Memory-Management/
Debe asumir que hace algo útil y llamar a Dispose incluso si no hace nada en las encarnaciones actuales de .NET Framework. No hay garantía de que siga siendo así en futuras versiones, lo que provocará un uso ineficiente de los recursos.
Incluso si un objeto no tiene recursos no administrados, su eliminación podría ayudar a GC al romper los gráficos de objetos. En general, si un objeto implementa IDisposable, se debe llamar a Dispose().
Si Dispose() realmente hace algo o no depende de la clase dada. En el caso de DataSet, la implementación de Dispose() se hereda de MarshalByValueComponent. Se elimina del contenedor y llama al evento Disposed. El código fuente está a continuación (desmontado con .NET Reflector):
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
lock (this)
{
if ((this.site != null) && (this.site.Container != null))
{
this.site.Container.Remove(this);
}
if (this.events != null)
{
EventHandler handler = (EventHandler) this.events[EventDisposed];
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
}
}
¿Creas las DataTables tú mismo? Porque generalmente no es necesario iterar a través de los elementos secundarios de cualquier Objeto (como en DataSet.Tables), ya que es trabajo del Padre eliminar todos sus miembros secundarios.
Generalmente, la regla es: si lo creó e implementa IDisposable, deséchelo. Si NO lo creó, NO lo deseche, ese es el trabajo del objeto principal. Pero cada objeto puede tener reglas especiales, consulta la Documentación.
Para .NET 3.5, dice explícitamente "Deséchelo cuando ya no lo use", así que eso es lo que yo haría.
Llamo a disponer cada vez que un objeto implementa IDisposeable. Está ahí por una razón.
Los conjuntos de datos pueden consumir mucha memoria. Cuanto antes se puedan marcar para su limpieza, mejor.
actualizar
Han pasado 5 años desde que respondí esta pregunta. Todavía estoy de acuerdo con mi respuesta. Si hay un método de eliminación, se debe llamar cuando haya terminado con el objeto. La interfaz IDispose se implementó por una razón.