ACTUALIZACIÓN : utilicé esta pregunta como base para un artículo que se puede encontrar aquí ; consúltelo para una discusión adicional sobre este tema. ¡Gracias por la buena pregunta!
Aunque la respuesta de Schabse es, por supuesto, correcta y responde a la pregunta que se hizo, hay una variante importante en su pregunta que no hizo:
¿Qué sucede si se font4 = new Font()
lanza después de que el constructor asignó el recurso no administrado pero antes de que el ctor regrese y complete font4
con la referencia?
Déjame aclararlo un poco más. Supongamos que tenemos:
public sealed class Foo : IDisposable
{
private int handle = 0;
private bool disposed = false;
public Foo()
{
Blah1();
int x = AllocateResource();
Blah2();
this.handle = x;
Blah3();
}
~Foo()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (this.handle != 0)
DeallocateResource(this.handle);
this.handle = 0;
this.disposed = true;
}
}
}
Ahora tenemos
using(Foo foo = new Foo())
Whatever(foo);
Esto es lo mismo que
{
Foo foo = new Foo();
try
{
Whatever(foo);
}
finally
{
IDisposable d = foo as IDisposable;
if (d != null)
d.Dispose();
}
}
OKAY. Supongamos Whatever
lanzamientos. Luego, el finally
bloque se ejecuta y el recurso se desasigna. No hay problema.
Supongamos Blah1()
lanzamientos. Luego, el lanzamiento ocurre antes de que se asigne el recurso. El objeto ha sido asignado pero el ctor nunca regresa, por foo
lo que nunca se completa. try
Nunca ingresamos, por lo que nunca ingresamos finally
tampoco. La referencia del objeto se ha quedado huérfana. Finalmente, el GC lo descubrirá y lo pondrá en la cola del finalizador. handle
sigue siendo cero, por lo que el finalizador no hace nada. Observe que se requiere que el finalizador sea robusto frente a un objeto que se está finalizando cuyo constructor nunca se completó . Usted está obligado a escribir finalizadores que son tan fuerte. Esta es otra razón más por la que debería dejar los finalizadores de escritura a expertos y no intentar hacerlo usted mismo.
Supongamos Blah3()
lanzamientos. El lanzamiento ocurre después de que se asigna el recurso. Pero nuevamente, foo
nunca se completa, nunca ingresamos finally
y el objeto es limpiado por el hilo del finalizador. Esta vez, el identificador no es cero y el finalizador lo limpia. De nuevo, el finalizador se ejecuta en un objeto cuyo constructor nunca tuvo éxito, pero el finalizador se ejecuta de todos modos. Obviamente debe porque esta vez, tenía trabajo que hacer.
Ahora suponga que Blah2()
lanza. ¡El lanzamiento ocurre después de que se asigna el recurso pero antes de que handle
se complete! Nuevamente, el finalizador se ejecutará, pero ahora handle
sigue siendo cero y ¡filtramos el controlador!
Debe escribir un código extremadamente inteligente para evitar que ocurra esta fuga. Ahora, en el caso de su Font
recurso, ¿a quién diablos le importa? Filtramos un identificador de fuente, gran cosa. Pero si necesita de manera absolutamente positiva que todos los recursos no administrados se limpien sin importar el momento de las excepciones, entonces tiene un problema muy difícil en sus manos.
El CLR tiene que resolver este problema con bloqueos. Desde C # 4, los bloqueos que usan la lock
declaración se han implementado así:
bool lockEntered = false;
object lockObject = whatever;
try
{
Monitor.Enter(lockObject, ref lockEntered);
lock body here
}
finally
{
if (lockEntered) Monitor.Exit(lockObject);
}
Enter
se ha escrito con mucho cuidado para que, independientemente de las excepciones que se generen , lockEntered
se establezca en verdadero si y solo si el bloqueo se tomó realmente. Si tiene requisitos similares, entonces lo que necesita es escribir:
public Foo()
{
Blah1();
AllocateResource(ref handle);
Blah2();
Blah3();
}
y escriba AllocateResource
inteligentemente como Monitor.Enter
para que, pase lo que pase en el interior AllocateResource
, handle
se rellene si y sólo si es necesario desasignarlo.
Describir las técnicas para hacerlo está más allá del alcance de esta respuesta. Consulte a un experto si tiene este requisito.