miércoles, 26 de diciembre de 2007

Patrón Dispose

Cuando programaba en lenguajes de bajo nivel, como C y C++, todo el mundo tenía mucho cuidado en liberar los recursos que utilizaba. Pero desde la llegada de los recolectores de basura, es algo que se ha ido olvidando. E incluso ignorando por las nuevas generaciones de programadores.

El recolector de basura en .Net hay que reconocer que quita mucho trabajo. Pero hay que echarle una mano para que su funcionamiento mejore. Me he encontrado servicios en producción que consumen 1Gb de RAM. Si 1Gb para un solo servicio. Y ya sé que .Net consume mucha memoria, pero no es necesaria tanta. Con una correcta utilización del interface IDisposable se puede reducir el consumo de memoria de aplicaciones administradas en un 10%, de lo que consumen sin tenerlo en cuenta.

Una primera forma de ayudar al recolector de basura, que en la mayoría de los casos se respeta es llamar al método Dispose de un objeto, cuando este se deja de usar. O utilizar la palabra clave using, que en definitiva hace lo mismo.


using (FileStream fs = File.Create(path))
{
    ...
}


Que es la forma abreviada de


FileStream fs;
try
{
    FileStream fs = File.Create(path);
    ...
}
finally
{
    fs.Dispose();
}

Lo que no es tan abitual, pero si tan importante es implementar corectamente el interface IDisposable. Todas las clases que hacen referencia a clases que implementan IDisposable deberian implementarla.

Para implementar IDisposable solo es necesario implementar la propiedad Dispose(), pero en la practica, es recomendable hacerlo de la siguiente manera:

public class Base : IDisposable
{
    DisposableObject obj1;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if(obj1 != null)
            obj1.Dispose();
        }
        / Liberar objetos no mánejados.
    }

    ~Base()
    {
        ispose(false);
    }
}

Al implementarlo de esta forma conseguimos lo siguiente:
  1. La clase es heredable de la siguiente manera:

    public class Base : IDisposable
    {
        DisposableObject obj1;
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if(obj1 != null)
                obj1.Dispose();
            }
            / Liberar objetos no mánejados.
        }
    
        ~Base()
        {
            ispose(false);
        }
    }
  2. Al llamar a GC.SuppressFinalize se le indica al recolector de basura que no llame al finalizador del objeto, con lo que la liberación de recurso por parte del recolector de basura es mucho más rápida.
  3. Permite definir un destructor de la clase. Aunque no es recomendable usarlo salvo en clases que utilicen recursos no administrados. No es recomendable definir destructores que no hagan nada, ya que dificultan la labor al recolector de basura.

En caso de que se quiera declarar una clase que no sea heredable, no nos permitirá definir el método Dispose como virtual. Con lo que se hará de la siguiente forma:

public class sealer Base : IDisposable
{

    DisposableObject obj1;       

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected void Dispose(bool disposing)
    {
        if (disposing)
        { 
            (obj1 != null)
            obj1.Dispose();
        }
        // Liberar objetos no mánejados.
    }

    ~Base()
    {
        Dispose(false);
    }
}

1 comentario:

vtortola dijo...
Este comentario ha sido eliminado por un administrador del blog.