csharp-que-son-idisposables

Cómo usar IDisposable en C#

IDisposable es una interfaz definida en el espacio de nombres System que contiene un solo método, Dispose.

La finalidad de esta interfaz es proporcionar una manera explícita de liberar recursos no administrados de forma determinista, garantizando que estos recursos se liberen tan pronto como dejen de ser necesarios.

En C#, el Garbage Collector (recolector de basura) se encarga de liberar la memoria asignada a los objetos una vez que ya no son necesarios. Sin embargo, existen recursos no administrados, como conexiones de bases de datos, archivos, sockets, etc., que no son administrados automáticamente por el Garbage Collector.

Si no se liberan adecuadamente, estos recursos pueden provocar fugas de memoria y otros problemas de rendimiento. Implementar IDisposable permite a los desarrolladores liberar estos recursos de manera explícita y controlada.

La interfaz IDisposable proporciona una forma de liberar estos recursos no administrados de manera explícita, lo que permite un mejor control sobre la administración de recursos en una aplicación.

Interfaz IDisposable

La interfaz IDisposable se define de la siguiente manera:

namespace System
{
    public interface IDisposable
    {
        void Dispose();
    }
}

Cuando una clase implementa IDisposable, se compromete a proporcionar una implementación del método Dispose, en el cual se liberarán los recursos no administrados.

Ejemplo básico

Supongamos que tenemos una clase que maneja un archivo. Queremos asegurarnos de que el archivo se cierre correctamente cuando ya no sea necesario.

Implementamos IDisposable para lograr esto:

public class ManejadorDeArchivo : IDisposable
{
    private FileStream _archivo;

    public ManejadorDeArchivo(string ruta)
    {
        _archivo = new FileStream(ruta, FileMode.OpenOrCreate);
    }

    public void Escribir(string mensaje)
    {
        byte[] info = new UTF8Encoding(true).GetBytes(mensaje);
        _archivo.Write(info, 0, info.Length);
    }

    public void Dispose()
    {
        _archivo?.Close();
        _archivo?.Dispose();
    }
}

En este ejemplo, el método Dispose cierra y libera el FileStream utilizado por la clase ManejadorDeArchivo.

Uso con using

Para simplificar la llamada a Dispose, C# proporciona la instrucción using, que asegura que Dispose se llame automáticamente al final del bloque de using:

using (var manejador = new ManejadorDeArchivo("ruta_del_archivo.txt"))
{
    manejador.Escribir("Hola, mundo!");
}

Este código garantiza que el método Dispose de ManejadorDeArchivo se llame automáticamente, incluso si ocurre una excepción dentro del bloque using.

Uso de GC.SuppressFinalize

Cuando se llama a Dispose explícitamente, es una buena práctica usar GC.SuppressFinalize para evitar que el recolector de basura llame al destructor, optimizando así el rendimiento.

Implementación avanzada con destructor

En algunos casos, es necesario liberar tanto recursos administrados como no administrados. Para ello, se puede implementar un patrón de eliminación más avanzado que combine Dispose y un destructor (finalizador):

public class RecursoAvanzado : IDisposable
{
    private IntPtr _recursoNoAdministrado;
    private bool _disposed = false;

    public RecursoAvanzado(IntPtr recurso)
    {
        _recursoNoAdministrado = recurso;
    }

    ~RecursoAvanzado()
    {
        Dispose(false);
    }

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

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                // Liberar recursos administrados
            }
            // Liberar recursos no administrados
            if (_recursoNoAdministrado != IntPtr.Zero)
            {
                // Liberar el recurso no administrado
                _recursoNoAdministrado = IntPtr.Zero;
            }
            _disposed = true;
        }
    }
}

En este ejemplo,

  • El método Dispose(bool disposing) gestiona la liberación tanto de recursos administrados como no administrados
  • El destructor llama a Dispose(false) para asegurarse de que los recursos no administrados se liberen si Dispose no se ha llamado explícitamente.