system-io-csharp

Cómo gestionar archivos y directorios con System.IO en C#

  • 4 min

System.IO es el espacio de nombres de .NET para trabajar con archivos, directorios y flujos de datos.

Cualquier programa útil tarde o temprano necesita salir de la memoria RAM y “tocar tierra”: leer un archivo de configuración, guardar un registro de errores (log) o procesar una lista de imágenes en una carpeta.

Este namespace es inmenso, pero hoy nos centraremos en la gestión de archivos y directorios. Y lo primero que debemos entender es una decisión de diseño fundamental de Microsoft que confunde a muchos: la dualidad entre Clases Estáticas y Clases de Instancia.

¿Clases estáticas o de instancia?

Para manejar archivos y carpetas, .NET nos da dos pares de clases que parecen hacer lo mismo:

  1. Estáticas (Static): File y Directory.
  2. De Instancia (Instance): FileInfo y DirectoryInfo.

¿Cuál uso? ¿File.Exists(...) o new FileInfo(...).Exists?

Son métodos de “disparar y olvidar”. Cada vez que llamas a un método, el sistema tiene que ir al disco, comprobar permisos y realizar la acción.

  • Uso: Ideal para operaciones únicas o esporádicas.
  • Ejemplo: Comprobar si un archivo existe una sola vez.

Creas un objeto que representa el archivo en memoria. Este objeto mantiene metadatos del archivo y permite consultarlos de forma más cómoda.

  • Uso: Ideal si vas a realizar múltiples operaciones sobre el mismo archivo (leer atributos, luego fecha, luego tamaño). Si el archivo cambia desde fuera, puedes llamar a Refresh() para actualizar esos metadatos.

Regla general: Si vas a hacer una sola cosa, usa File o Directory (escribirás menos código). Si vas a hacer un bucle o consultar varias propiedades del mismo elemento, usa FileInfo o DirectoryInfo.

Gestionar directorios (Directory)

Vamos a ver cómo crear, borrar y listar carpetas.

Crear y comprobar

El método CreateDirectory es idempotente. Esto significa que si el directorio ya existe, no hace nada ni lanza error. Es muy seguro.

string rutaLogs = Path.Combine(Directory.GetCurrentDirectory(), "logs");

if (!Directory.Exists(rutaLogs))
{
    Directory.CreateDirectory(rutaLogs);
}
Copied!

Listar archivos de forma eficiente

Tenemos dos formas de obtener los archivos de una carpeta, y la diferencia importa en directorios grandes:

  1. GetFiles(): Devuelve un string[].
  2. EnumerateFiles(): Devuelve un IEnumerable<string>.

Si tienes una carpeta con 100.000 fotos:

  • GetFiles esperará a encontrar las 100.000, cargará todos los nombres en memoria (un array gigante) y entonces te devolverá el control. Es lento y consume mucha RAM.
  • EnumerateFiles te devuelve el primer archivo en cuanto lo encuentra, luego el segundo… Permite empezar a trabajar inmediatamente y usa muy poca memoria.
// Mejor para carpetas grandes
foreach (string archivo in Directory.EnumerateFiles(rutaLogs, "*.txt"))
{
    Console.WriteLine($"Procesando: {archivo}");
}
Copied!

Gestionar archivos (File)

Las operaciones básicas son muy directas:

string origen = "documento.txt";
string destino = "documento_backup.txt";

if (File.Exists(origen))
{
    // Copiar (overwrite = true permite sobrescribir si existe)
    File.Copy(origen, destino, true);

    // Mover (o renombrar)
    File.Move(destino, "documento_archivado.txt");

    // Borrar
    File.Delete(origen);
}
Copied!

¡Cuidado con el bloqueo! System.IO es famoso por lanzar excepciones si intentas acceder a un archivo que otro programa (o tu propio programa) tiene abierto. Asegúrate siempre de cerrar los archivos o usar bloques using.

Lectura y escritura rápida

Para operaciones sencillas (archivos de texto pequeños), la clase File tiene métodos auxiliares que abren, leen/escriben y cierran el archivo en una sola línea.

// Escribir texto (sobrescribe todo)
string contenido = "Hola Mundo desde C#";
File.WriteAllText("nota.txt", contenido);

// Añadir al final (Append)
File.AppendAllText("nota.txt", "\nNueva línea");

// Leer todo (Cuidado con archivos grandes, carga todo en RAM)
string textoLeido = File.ReadAllText("nota.txt");

// Leer línea a línea (Array)
string[] lineas = File.ReadAllLines("nota.txt");
Copied!

Streams: la base de todo

Los métodos anteriores (WriteAllText) son “azúcar sintáctico”. Por debajo, todo en System.IO funciona con Streams (flujos de datos).

Un Stream es como una tubería por la que pasan bytes. Para archivos grandes, debemos usar Streams para no saturar la memoria.

Para leer texto de forma eficiente usamos StreamReader y para escribir StreamWriter.

string ruta = "libro_gigante.txt";

// Usamos 'using' para asegurar que el archivo se cierra y libera recursos
using (StreamWriter sw = new StreamWriter(ruta))
{
    sw.WriteLine("Capítulo 1");
    sw.WriteLine("En un lugar de la Mancha...");
}

using (StreamReader sr = new StreamReader(ruta))
{
    string linea;
    // Leemos línea a línea sin cargar todo el libro en memoria
    while ((linea = sr.ReadLine()) != null)
    {
        Console.WriteLine(linea);
    }
}
Copied!

E/S asíncrona

El acceso a disco es lento. Extremadamente lento comparado con la CPU.

Si lees un archivo grande en el hilo principal de una aplicación gráfica (WPF, Windows Forms) o en una Web API, tu aplicación se congelará hasta que termine la lectura.

En aplicaciones con interfaz, APIs o servicios concurrentes, conviene preferir las versiones Async:

// No bloquea el hilo mientras espera al disco duro
string texto = await File.ReadAllTextAsync("datos.json");

// Escribir asíncronamente
await File.WriteAllTextAsync("log.txt", "Operación completada");
Copied!