La
interfaz IDisposable
La interfaz IDisposable nos provee de la forma de crear clases
desechables, que pueden ser desechadas de forma casi determinística. El método
Dispose que implementa no es un destructor, de hecho, no deja de
ser un simple método cualquiera con la particularidad de que su misión contractual (por el hecho de
pertenecer a IDisposable) es liberar los recursos administrados y no
administrados. Una vez implementada, podríamos “desechar” nuestra clase
invocando al método .Dispose ó usando nuestra clase dentro de una
sentencia using.
A bote pronto parece
sencillo pero aparecen varias casuísticas especiales, de las cuales las más
destacables son:
- ¿Qué sucede si se llama a Dispose mientras
ya se esta ejecutando otro Dispose? Pués es fácil de predecir, si se
intenta hacer un Stream.Close() de un Stream que ya ha sido destruido… se
dispararía una NullReferenceException.
- ¿Qué sucede si se nos olvida hacer un
Dispose? Pues también es obvio, a no ser que tengamos definido un
destructor que también libere los recursos… cuando el GC recolecte esta
clase… quedarán sin liberar.
- ¿Qué sucede si tenemos Dispose y un
destructor con el mismo código duplicado para liberar recursos? Pues que
si llamamos a Dispose, después cuando el GC detecte que hay un destructor
y lo llame, si no hay forma de averiguar si los recursos se han liberado ó
no… podría ó podrían dispararse NullReferenceException al intentar liberar
recursos ya liberados. Además del problema intrínseco de diseño que
implica tener código duplicado.
- ¿Y si apuntan a un código en común para
liberar recursos? Pues volviendo a la casuística #3, si no tenemos forma
de controlar si se ha liberado ya podríamos obtener el mismo resultado.
- ¿Y si pudiese controlar cuando han sido
liberados? Pues el único defecto que quedaría es el hecho de que el GC
encolaría la clase al recolectarla por el mero hecho de tener un
destructor definido, siendo esto no un problema, pero si mejorable.
Estos
cinco casos quedan cubiertos con la correcta implementación del patrón
desechable:
class MiClase :
IDisposable
{
private bool disposing;
//
// … resto del código …
//
///
<summary>
///
Método de IDisposable para desechar la clase.
///
</summary>
public void
Dispose()
{
// Llamo al método que contiene la lógica
// para liberar los recursos de esta clase.
Dispose(true);
}
///
<summary>
///
Método sobrecargado de Dispose que será el que
///
libera los recursos, controla que solo se ejecute
/// dicha
lógica una vez y evita que el GC tenga que
///
llamar al destructor de clase.
/// </summary>
/// <param
name=”b”></param>
protected virtual void Dispose(bool b)
{
// Si no se esta destruyendo ya…
if (!disposing)
{
// La marco como desechada ó desechandose,
// de forma que no se puede ejecutar este código
// dos veces.
disposing = true;
// Indico al GC que no llame al destructor
// de esta clase al recolectarla.
GC.SuppressFinalize(this);
// … libero los recursos…
}
}
///
<summary>
///
Destructor de clase.
/// En
caso de que se nos olvide “desechar” la clase,
/// el GC
llamará al destructor, que tambén ejecuta la lógica
/// anterior
para liberar los recursos.
///
</summary>
~MiClase()
{
// Llamo al método que contiene la lógica
// para liberar los recursos de esta clase.
Dispose(true);
}
}
·
La variable booleana
‘disposing’ controla si se ha desechado ó se esta desechando el objeto para que
no se ejecute dos veces.
·
El método ‘Dispose(bool b)’
es realmente quien libera los recursos, primero comprueba que la instancia no
ha sido desechada ó este desechandose, libera los recursos y ejecuta
‘GC.SuppressFinalize(this)’ para que se suprima el constructor de esta clase y
el GC pueda recolectarla sin más. De esta forma solo se ejecutaría este método
una vez por instancia.
·
El método ‘Dispose()’ llama
a su homónimo sobrecargado ‘Dispose(bool b)’ para que deseche la instancia. El
destructor de clase también llama a ‘Dispose(bool b)’ para que deseche la
instancia. De esta forma, tanto si desechamos el objeto intencionadamente, como
si se nos olvida y lo destruye el GC, se ejecutará el mismo método y solo una
vez por instancia.
DEMOSTRACIÓN
En Visual Studio vamos a dar click en menú File à New à Project
En la Ventana emergente le damos click a Window,
seleccionamos la opción “Console Application” y espacio Name colocamos el
nombre del proyecto que para el Ejemplo es “Demo 11 20483” y luego presionamos
el botón OK
En el editor de código, debajo del método Main()
crearemos la clase ‘textfile1’ que implementa la interfaz IDisposable.
En el método Main() codificaremos lo siguiente:
La salida de la consola
sería la siguiente: