Objetos desechables con la interfaz IDisposable

by Valeriano Tortola 28. agosto 2007 11:06

En Julio apareció en la sección CLR Inside Out del MSDN Magazine un artículo de Shawn Farkas acerca del correcto uso de la interfaz IDisposable (en castellano), explicando el porqué de cada cosa. No puedo explicarlo mejor y más detalladamente de lo que lo ha hecho Shawn Farkas, pero si más resumido :D

Una de las cuestiones críticas a la hora de optimizar el uso de memoria es que el GC se ejecuta de forma no determinística, esto es, que no podemos predecir cuando se va a realizar, y aunque podemos invocarlo explícitamente con GC.Collect(), no es recomendable.

Los destructores de clase definidos con un circunflejo, el nombre de la clase y sin modificador de acceso alguno (ie: ~Miclase() ), corresponde al código que ejecutará el GC (si existe) cuando destruya el objeto. Este método no puede ser invocado, por lo que no podemos llamarlo para realizar la destrucción determinística del mismo. Si el GC se dispone a recolectar este objeto y no tiene dicho destructor, es recolectado directamente, en caso de tener un destructor pasa a una cola de destrucción para ser destruido a posteriori, lo cual nos dice que no siempre va a ser óptimo definir un constructor, únicamente cuando sea necesario.

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:

  1. ¿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.
  2. ¿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.
  3. ¿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.
  4. ¿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.
  5. ¿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.
¿Cuando usar IDisposable en una clase? Mira este link.

Tags: , , ,

.NET 2.0 | C# 2.0

Comentarios

07/10/2007 19:39:08 #

pingback

Pingback from elbruno.com

Cliente FTP asincrono - vtortola

elbruno.com |

12/07/2008 4:45:38 #

trackback

Trackback from Pensando en asíncrono

Desechable o no desechable

Pensando en asíncrono |

Comentarios no permitidos