Cargando...

IDisposable y el patrón using en C#

Aprende IDisposable y el patrón using en C# para gestionar recursos correctamente y evitar fugas de memoria.

En C#, la interfaz IDisposable está diseñada para liberar correctamente los recursos utilizados fuera de la memoria (por ejemplo, archivos, conexiones de red, bases de datos, objetos GDI, etc.). En lugar de esperar a que el sistema los limpie automáticamente, se realiza una limpieza determinista (predecible) mediante el método Dispose y la estructura using.


¿Qué es IDisposable?

La interfaz IDisposable contiene solo un método:


public interface IDisposable
{
    void Dispose();
}

Cuando una clase implementa IDisposable, debe liberar los recursos administrados y no administrados que posee dentro del método Dispose(). El recolector de basura (GC) de .NET no puede limpiar automáticamente los recursos no administrados, por lo que es esencial llamar a Dispose().


Ejemplo simple de IDisposable

En el siguiente ejemplo, la clase EscritorArchivo abre un flujo de archivo e implementa IDisposable. Cuando se llama a Dispose(), el archivo se cierra automáticamente.


using System;
using System.IO;

class EscritorArchivo : IDisposable
{
    private readonly StreamWriter _writer;
    private bool _disposed = false;

    public EscritorArchivo(string rutaArchivo)
    {
        _writer = new StreamWriter(rutaArchivo);
    }

    public void Escribir(string texto)
    {
        if (_disposed)
            throw new ObjectDisposedException(nameof(EscritorArchivo));

        _writer.WriteLine(texto);
    }

    public void Dispose()
    {
        if (!_disposed)
        {
            _writer.Close();
            _writer.Dispose();
            _disposed = true;
            Console.WriteLine("Archivo cerrado y recursos liberados.");
        }
    }
}

class Program
{
    static void Main()
    {
        var escritor = new EscritorArchivo("test.txt");
        escritor.Escribir("¡Hola Mundo!");
        escritor.Dispose(); // recursos liberados
    }
}

Gestión de recursos con using

La palabra clave using permite limpiar automáticamente los objetos que implementan IDisposable. Cuando termina el bloque using, el método Dispose() se llama automáticamente.


using (var archivo = new StreamWriter("log.txt"))
{
    archivo.WriteLine("Aplicación iniciada: " + DateTime.Now);
} // Dispose() se llama automáticamente aquí

De esta forma, no se producen fugas de recursos, incluso si ocurre un error. Un bloque using es equivalente a una estructura try/finally:


var archivo = new StreamWriter("log.txt");
try
{
    archivo.WriteLine("Aplicación iniciada.");
}
finally
{
    archivo.Dispose();
}

Varios using en una misma línea

Desde C# 8.0, la característica “using declaration” permite una sintaxis más limpia. No se necesitan llaves, y Dispose() se llama automáticamente al final del ámbito.


using var archivo = new StreamWriter("output.txt");
archivo.WriteLine("Este archivo se cerrará automáticamente al finalizar el programa.");

Patrón Dispose (uso avanzado)

Si una clase contiene recursos no administrados o puede ser heredada por otras clases que también posean objetos IDisposable, debe implementar el patrón Dispose. Este patrón utiliza Dispose(bool disposing) para liberar por separado los recursos administrados y no administrados.


using System;

class AdministradorRecursos : IDisposable
{
    private IntPtr _recursoNoAdministrado; // ejemplo: handle de archivo, socket, objeto GDI
    private bool _disposed = false;

    public AdministradorRecursos()
    {
        _recursoNoAdministrado = IntPtr.Zero; // inicialización de ejemplo
    }

    // Método público Dispose
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // evita la llamada al finalizador
    }

    // Método protegido virtual, extensible por clases derivadas
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {
            // liberar recursos administrados aquí
        }

        // liberar recursos no administrados aquí
        if (_recursoNoAdministrado != IntPtr.Zero)
        {
            // ejemplo: CloseHandle(_recursoNoAdministrado);
            _recursoNoAdministrado = IntPtr.Zero;
        }

        _disposed = true;
    }

    // Finalizador (garantía para recursos no administrados)
    ~AdministradorRecursos()
    {
        Dispose(false);
    }
}

Esta estructura evita llamadas innecesarias al finalizador mediante GC.SuppressFinalize() y garantiza la liberación segura de los recursos no administrados.


Gestión asíncrona de recursos: IAsyncDisposable

Introducida en C# 8.0, la interfaz IAsyncDisposable se utiliza para limpiar recursos de forma asíncrona. Por ejemplo, una conexión de red o un flujo pueden cerrarse de manera asíncrona.


using System;
using System.IO;
using System.Threading.Tasks;

class EscritorLog : IAsyncDisposable
{
    private readonly StreamWriter _writer = new StreamWriter("async_log.txt");

    public async ValueTask DisposeAsync()
    {
        await _writer.FlushAsync();
        _writer.Dispose();
        Console.WriteLine("Escritor de log asíncrono cerrado.");
    }

    public async Task EscribirAsync(string mensaje)
    {
        await _writer.WriteLineAsync(mensaje);
    }
}

class Program
{
    static async Task Main()
    {
        await using var log = new EscritorLog();
        await log.EscribirAsync("Programa iniciado.");
    }
}

Errores comunes y consideraciones


Ejemplo: servicio de gestión de archivos

En el siguiente ejemplo, una clase de servicio administra recursos como FileStream y StreamReader. Gracias a using, los archivos se cierran automáticamente cuando se completa la operación.


using System;
using System.IO;

class ServicioArchivo
{
    public string Leer(string ruta)
    {
        using var sr = new StreamReader(ruta);
        return sr.ReadToEnd();
    }

    public void Escribir(string ruta, string datos)
    {
        using var sw = new StreamWriter(ruta, append: true);
        sw.WriteLine(datos);
    }
}

class Program
{
    static void Main()
    {
        var servicio = new ServicioArchivo();
        servicio.Escribir("reporte.txt", "Nuevo registro agregado.");
        Console.WriteLine(servicio.Leer("reporte.txt"));
    }
}

TL;DR

  • IDisposable permite liberar manualmente los recursos mediante Dispose().
  • La estructura using llama automáticamente a Dispose() y evita fugas de recursos.
  • El patrón Dispose limpia de forma segura los recursos administrados y no administrados.
  • IAsyncDisposable cierra recursos de forma asíncrona (por ejemplo, archivos, red).
  • GC.SuppressFinalize() mejora el rendimiento evitando llamadas innecesarias al finalizador.

Artículos relacionados