¿Cuál es la diferencia entre usar IDisposable y un destructor en C #?

101

¿Cuándo implementaría IDispose en una clase en lugar de un destructor? Leí este artículo , pero todavía me pierdo el punto.

Mi suposición es que si implemento IDispose en un objeto, puedo 'destruirlo' explícitamente en lugar de esperar a que el recolector de basura lo haga. ¿Es esto correcto?

¿Eso significa que siempre debería llamar explícitamente a Dispose en un objeto? ¿Cuáles son algunos ejemplos comunes de esto?

Jordan Parmer
fuente
5
De hecho, debe llamar a Dispose en cada objeto Disposable. Puede hacerlo fácilmente usando la usingconstrucción.
Luc Touraille
Ah, eso tiene sentido. Siempre me había preguntado por qué se usaba la declaración 'using' para flujos de archivos. Sé que tuvo algo que ver con el alcance del objeto, pero no lo puse en contexto con la interfaz IDisposable.
Jordan Parmer
5
Un punto importante para recordar es que un finalizador nunca debe acceder a ningún miembro administrado de una clase, ya que es posible que esos miembros ya no sean referencias válidas.
Dan Bryant

Respuestas:

126

Un finalizador (también conocido como destructor) es parte de la recolección de basura (GC); es indeterminado cuando (o incluso si) esto sucede, ya que GC ocurre principalmente como resultado de la presión de la memoria (es decir, necesita más espacio). Los finalizadores generalmente solo se usan para limpiar recursos no administrados , ya que los recursos administrados tendrán su propia recolección / eliminación.

Por tanto, IDisposablese utiliza para limpiar de forma determinista los objetos, es decir, ahora. No recopila la memoria del objeto (que todavía pertenece a GC), pero se usa, por ejemplo, para cerrar archivos, conexiones de bases de datos, etc.

Hay muchos temas anteriores sobre esto:

Finalmente, tenga en cuenta que no es raro que un IDisposableobjeto también tenga un finalizador; en este caso, Dispose()normalmente llama GC.SuppressFinalize(this), lo que significa que GC no ejecuta el finalizador, simplemente tira la memoria (mucho más barato). El finalizador aún se ejecuta si se olvida Dispose()del objeto.

Marc Gravell
fuente
¡Gracias! Eso tiene mucho sentido. Agradezco mucho la gran respuesta.
Jordan Parmer
27
Una cosa extra que decir. No agregue un finalizador a su clase a menos que realmente lo necesite. Si agrega un finalizador (destructor), el GC debe llamarlo (incluso un finalizador vacío) y para llamarlo, el objeto siempre sobrevivirá a una recolección de basura gen 1. Esto impedirá y ralentizará el GC. Eso es lo que Marc dice que llame a SuppressFinalize en el código anterior
Kevin Jones
1
Por lo que Finalizar es liberar recursos no administrados. ¿Pero Dispose podría usarse para liberar recursos administrados y no administrados?
Dark_Knight
2
@Dark sí; porque 6 niveles más abajo en la cadena de administradores podrían ser uno no administrado que necesita una limpieza inmediata
Marc Gravell
1
@KevinJones Los objetos con un finalizador están garantizados para sobrevivir a la generación 0, no a la 1, ¿verdad? Leí eso en un libro llamado .NET Performance.
David Klempfner
25

La función del Finalize()método es garantizar que un objeto .NET pueda limpiar los recursos no administrados cuando se recolecta la basura . Sin embargo, los objetos como las conexiones de bases de datos o los controladores de archivos deben liberarse lo antes posible, en lugar de depender de la recolección de basura. Para eso, debe implementar la IDisposableinterfaz y liberar sus recursos en el Dispose()método.

Igal Tabachnik
fuente
9

Hay una muy buena descripción en MSDN :

El uso principal de esta interfaz es liberar recursos no administrados . El recolector de basura libera automáticamente la memoria asignada a un objeto administrado cuando ese objeto ya no se usa. Sin embargo, no es posible predecir cuándo ocurrirá la recolección de basura . Además, el recolector de basura no tiene conocimiento de recursos no administrados como identificadores de ventanas o archivos abiertos y transmisiones.

Utilice el método Dispose de esta interfaz para liberar explícitamente recursos no administrados junto con el recolector de basura. El consumidor de un objeto puede llamar a este método cuando el objeto ya no es necesario.

abatishchev
fuente
1
Una de las principales debilidades de esa descripción es que MS da ejemplos de recursos no administrados, pero por lo que he visto, nunca define el término. Dado que los objetos administrados generalmente solo se pueden usar dentro del código administrado, uno podría pensar que las cosas que se usan en el código no administrado son recursos no administrados, pero eso no es realmente cierto. Una gran cantidad de código no administrado no usa ningún recurso, y algunos tipos de recursos no administrados, como los eventos, existen solo en el universo de código administrado.
supercat
1
Si un objeto de corta duración se suscribe a un evento de un objeto de larga duración (por ejemplo, solicita ser notificado de cualquier cambio que ocurra dentro de la vida útil del objeto de corta duración), dicho evento debe considerarse un recurso no administrado, ya que no cancelar la suscripción del evento haría que la vida útil del objeto de corta duración se extendiera a la del objeto de larga duración. Si muchos miles o millones de objetos de corta duración se suscribieron a un evento pero se abandonaron sin cancelar la suscripción, eso podría causar una fuga de memoria o CPU (ya que el tiempo requerido para procesar cada suscripción aumentaría).
supercat
1
Otro escenario que involucra recursos no administrados dentro del código administrado sería la asignación de objetos desde grupos. Especialmente si el código necesita ejecutarse en .NET Micro Framework (cuyo recolector de basura es mucho menos eficiente que el de las máquinas de escritorio), puede ser útil que el código tenga, por ejemplo, una matriz de estructuras, cada una de las cuales puede estar marcada como "usada". o "gratis". Una solicitud de asignación debe encontrar una estructura que esté actualmente marcada como "libre", marcarla como "usada" y devolverle un índice; una solicitud de liberación debe marcar una estructura como "libre". Si una solicitud de asignación devuelve, por ejemplo, 23, entonces ...
supercat
1
... si el código nunca notifica al propietario de la matriz que ya no necesita el elemento n. ° 23, esa ranura de matriz nunca será utilizable por ningún otro código. Tal asignación manual fuera de las ranuras de la matriz no se usa con mucha frecuencia en el código de escritorio, ya que el GC es bastante eficiente, pero en el código que se ejecuta en Micro Framework puede marcar una gran diferencia.
supercat
8

Lo único que debería estar en un destructor de C # es esta línea:

Dispose(False);

Eso es. Nada más debería estar en ese método.

Jonathan Allen
fuente
3
Este es el patrón de diseño propuesto por Microsoft en la documentación de .NET, pero no lo use cuando su objeto no es IDisposable. msdn.microsoft.com/en-us/library/fs2xkftw%28v=vs.110%29.aspx
Zbyl
1
No puedo pensar en ninguna razón para ofrecer una clase con un finalizador que no tenga también un método Dispose.
Jonathan Allen
4

Su pregunta sobre si debe llamar siempre o no Disposesuele ser un debate acalorado. Consulte este blog para obtener una perspectiva interesante de personas respetadas en la comunidad .NET.

Personalmente, creo que la posición de Jeffrey Richter de que llamar Disposeno es obligatorio es increíblemente débil. Da dos ejemplos para justificar su opinión.

En el primer ejemplo, dice que llamar Disposea los controles de Windows Forms es tedioso e innecesario en los escenarios principales. Sin embargo, no menciona que, en Disposerealidad, los contenedores de control lo llaman automáticamente en esos escenarios convencionales.

En el segundo ejemplo, afirma que un desarrollador puede asumir incorrectamente que la instancia de IAsyncResult.WaitHandledebe eliminarse de forma agresiva sin darse cuenta de que la propiedad inicializa perezosamente el identificador de espera, lo que genera una penalización de rendimiento innecesaria. Pero, el problema con este ejemplo es que el IAsyncResultmismo no se adhiere a las pautas publicadas por Microsoft para tratar con IDisposableobjetos. Es decir, si una clase tiene una referencia a un IDisposabletipo, la clase misma debería implementarla IDisposable. Si se IAsyncResultsigue esa regla, entonces su propio Disposemétodo podría tomar la decisión sobre cuál de sus miembros constituyentes necesita eliminarse.

Entonces, a menos que alguien tenga un argumento más convincente, me quedaré en el campo de "siempre llamar a Dispose" con el entendimiento de que habrá algunos casos marginales que surjan principalmente de malas elecciones de diseño.

Brian Gideon
fuente
3

Realmente es bastante simple. Sé que ha sido respondida, pero intentaré de nuevo, pero intentaré que sea lo más simple posible.

Por lo general, nunca se debe utilizar un destructor. Solo se ejecuta .net quiere que se ejecute. Solo se ejecutará después de un ciclo de recolección de basura. Es posible que nunca se ejecute durante el ciclo de vida de su aplicación. Por esta razón, nunca debe poner ningún código en un destructor que 'deba' ejecutarse. Tampoco puede confiar en que exista ningún objeto existente dentro de la clase cuando se ejecuta (es posible que ya se hayan limpiado ya que el orden en el que se ejecutan los destructores no está garantizado).

IDisposible debe usarse siempre que tenga un objeto que cree recursos que necesiten limpieza (es decir, identificadores de archivos y gráficos). De hecho, muchos argumentan que cualquier cosa que coloque en un destructor debería colocarse en IDisposable debido a las razones enumeradas anteriormente.

La mayoría de las clases llamarán a dispose cuando se ejecute el finalizador, pero esto simplemente está ahí como una protección y nunca se debe confiar en él. Debe desechar explícitamente todo lo que implemente IDisposable cuando haya terminado con él. Si implementa IDisposable, debe llamar a dispose en finalizer. Ver http://msdn.microsoft.com/en-us/library/system.idisposable.aspx para ver un ejemplo.

DaEagle
fuente
No, el recolector de basura nunca llama a Dispose (). Que sólo se llama al finalizador.
Marc Gravell
Arreglado eso. Se supone que las clases deben llamar a dispose en su finalizador, pero no es necesario.
DaEagle