El patrón .NET IDisposable implica que si escribe un finalizador e implementa IDisposable, su finalizador debe llamar explícitamente a Dispose. Esto es lógico, y es lo que siempre he hecho en las raras situaciones en las que se justifica un finalizador.
Sin embargo, qué sucede si solo hago esto:
class Foo : IDisposable
{
public void Dispose(){ CloseSomeHandle(); }
}
y no implemente un finalizador, ni nada. ¿El marco llamará al método Dispose para mí?
Sí, me doy cuenta de que esto suena tonto, y toda lógica implica que no lo hará, pero siempre he tenido 2 cosas detrás de mi cabeza que me han hecho sentir inseguro.
Hace unos años, alguien me dijo que, de hecho, haría esto, y que esa persona tenía un historial muy sólido de "conocer sus cosas".
El compilador / marco hace otras cosas 'mágicas' dependiendo de qué interfaces implemente (por ejemplo: foreach, métodos de extensión, serialización basada en atributos, etc.), por lo que tiene sentido que esto también sea 'mágico'.
Si bien he leído muchas cosas al respecto, y ha habido muchas cosas implícitas, nunca he podido encontrar una respuesta definitiva de Sí o No a esta pregunta.
fuente
Quiero enfatizar el punto de Brian en su comentario, porque es importante.
Los finalizadores no son destructores deterministas como en C ++. Como otros han señalado, no hay garantía de cuándo se llamará y, de hecho, si tiene suficiente memoria, si alguna vez se llamará.
Pero lo malo de los finalizadores es que, como dijo Brian, hace que su objeto sobreviva a una recolección de basura. Esto puede ser malo. ¿Por qué?
Como puede saber o no, el GC se divide en generaciones: Gen 0, 1 y 2, más el montón de objetos grandes. Dividir es un término suelto: obtienes un bloque de memoria, pero hay indicadores de dónde comienzan y terminan los objetos Gen 0.
El proceso de pensamiento es que probablemente usarás muchos objetos que durarán poco. Por lo tanto, deberían ser fáciles y rápidos para que el GC llegue a los objetos de Gen 0. Entonces, cuando hay presión de memoria, lo primero que hace es una colección Gen 0.
Ahora, si eso no resuelve suficiente presión, entonces regresa y realiza un barrido Gen 1 (rehaciendo Gen 0), y luego, si aún no es suficiente, realiza un barrido Gen 2 (rehaciendo Gen 1 y Gen 0). Por lo tanto, limpiar objetos de larga duración puede llevar un tiempo y ser bastante costoso (ya que sus hilos pueden estar suspendidos durante la operación).
Esto significa que si haces algo como esto:
Su objeto, pase lo que pase, vivirá para la Generación 2. Esto se debe a que el GC no tiene forma de llamar al finalizador durante la recolección de basura. Por lo tanto, los objetos que deben finalizarse se mueven a una cola especial para que los limpie un subproceso diferente (el subproceso finalizador, que si mata hace que sucedan todo tipo de cosas malas). Esto significa que sus objetos permanecen más tiempo y potencialmente obligan a más recolecciones de basura.
Por lo tanto, todo eso es solo para llevar a casa el punto que desea usar IDisposable para limpiar recursos siempre que sea posible y tratar seriamente de encontrar formas de usar el finalizador. Es en el mejor interés de su aplicación.
fuente
Ya hay mucha buena discusión aquí, y llego un poco tarde a la fiesta, pero yo mismo quería agregar algunos puntos.
Esa es la versión simple, pero hay muchos matices que pueden hacerte tropezar con este patrón.
En mi opinión, es mucho mejor evitar por completo tener cualquier tipo que contenga directamente referencias desechables y recursos nativos que puedan requerir finalización. SafeHandles proporciona una forma muy limpia de hacerlo al encapsular recursos nativos en desechables que proporcionan internamente su propia finalización (junto con una serie de otros beneficios, como eliminar la ventana durante P / Invoke, donde un controlador nativo podría perderse debido a una excepción asincrónica) .
Simplemente definir un SafeHandle hace que este Trivial:
Le permite simplificar el tipo que contiene a:
fuente
GC.SuppressFinalize
en este ejemplo. En este contexto, SuppressFinalize solo debería llamarse si seDispose(true)
ejecuta con éxito. SiDispose(true)
falla en algún momento después de que se suprime la finalización pero antes de que se limpien todos los recursos (especialmente los no administrados), aún así desea que se realice la finalización para realizar la mayor limpieza posible. Es mejor mover laGC.SuppressFinalize
llamada alDispose()
método después de la llamada aDispose(true)
. Consulte las Pautas de diseño del marco y esta publicación .No lo creo. Usted tiene control sobre cuándo se llama a Dispose, lo que significa que, en teoría, podría escribir un código de eliminación que haga suposiciones sobre (por ejemplo) la existencia de otros objetos. No tiene control sobre cuándo se llama al finalizador, por lo que sería dudoso que el finalizador llame automáticamente a Dispose en su nombre.
EDITAR: Me fui y probé, solo para asegurarme:
fuente
No en el caso que usted describe, pero el GC llamará al Finalizador por usted, si tiene uno.
SIN EMBARGO. La próxima recolección de basura, en lugar de ser recolectada, el objeto irá a la cola de finalización, todo se recolectará, luego se llamará finalizador. La próxima colección después de eso será liberada.
Dependiendo de la presión de memoria de su aplicación, es posible que no tenga un gc para la generación de ese objeto por un tiempo. Entonces, en el caso de, por ejemplo, una secuencia de archivos o una conexión de base de datos, es posible que tenga que esperar un tiempo para que el recurso no administrado se libere en la llamada del finalizador por un tiempo, causando algunos problemas.
fuente
No, no se llama.
Pero esto hace que no te olvides de deshacerte de tus objetos. Solo usa la
using
palabra clave.Hice la siguiente prueba para esto:
fuente
El GC no llamará a disponer. Se puede llamar a su finalizador, pero incluso esto no está garantizado en todas las circunstancias.
Vea este artículo para una discusión sobre la mejor manera de manejar esto.
fuente
La documentación sobre IDisposable ofrece una explicación bastante clara y detallada del comportamiento, así como un código de ejemplo. El GC NO llamará al
Dispose()
método en la interfaz, pero llamará al finalizador para su objeto.fuente
El patrón IDisposable fue creado principalmente para ser llamado por el desarrollador, si tiene un objeto que implementa IDispose, el desarrollador debe implementar la
using
palabra clave alrededor del contexto del objeto o llamar al método Dispose directamente.La seguridad del patrón es implementar el finalizador que llama al método Dispose (). Si no lo hace, puede crear algunas pérdidas de memoria, es decir: si crea algún contenedor COM y nunca llama al System.Runtime.Interop.Marshall.ReleaseComObject (comObject) (que se colocaría en el método Dispose).
No hay magia en el clr para llamar a los métodos de eliminación automáticamente, aparte de rastrear objetos que contienen finalizadores y almacenarlos en la tabla de finalizadores por el GC y llamarlos cuando el GC activa algunas heurísticas de limpieza.
fuente