Ir al contenido principal

Lectura y escritura de archivos de texto

El manejo de archivos de texto es una operación fundamental en el desarrollo de aplicaciones, permitiendo almacenar datos de manera persistente, crear registros de actividad, procesar documentos y intercambiar información entre sistemas. C# proporciona una amplia gama de clases y métodos en el espacio de nombres System.IO que facilitan estas operaciones de forma segura y eficiente.

La capacidad de leer y escribir archivos abre un mundo de posibilidades para nuestras aplicaciones: desde simples logs de depuración hasta complejos sistemas de procesamiento de datos. Dominar estas técnicas es esencial para cualquier desarrollador que quiera crear aplicaciones robustas y funcionales.

Fundamentos del sistema de archivos en .NET

Espacio de nombres System.IO

El espacio de nombres System.IO contiene las clases principales para operaciones de entrada/salida en .NET. Estas clases proporcionan funcionalidades para trabajar con archivos, directorios, rutas y flujos de datos.

Clase principal Propósito
File Métodos estáticos para operaciones básicas con archivos
FileInfo Información y operaciones sobre archivos específicos
Directory Métodos estáticos para operaciones con directorios
DirectoryInfo Información y operaciones sobre directorios específicos
Path Utilidades para manipular rutas de archivos
StreamReader Lectura de archivos de texto de forma eficiente
StreamWriter Escritura de archivos de texto de forma eficiente

Conceptos básicos de rutas

using System;
using System.IO;

// Trabajar con rutas de archivos de forma segura
string rutaCompleta = Path.Combine("C:", "datos", "archivo.txt");
string nombreArchivo = Path.GetFileName(rutaCompleta);
string extension = Path.GetExtension(rutaCompleta);
string directorio = Path.GetDirectoryName(rutaCompleta);
string sinExtension = Path.GetFileNameWithoutExtension(rutaCompleta);

Console.WriteLine($"Ruta completa: {rutaCompleta}");
Console.WriteLine($"Nombre: {nombreArchivo}");
Console.WriteLine($"Extensión: {extension}");
Console.WriteLine($"Directorio: {directorio}");
Console.WriteLine($"Sin extensión: {sinExtension}");

// Obtener directorios especiales del sistema
string documentos = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string escritorio = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string temporal = Path.GetTempPath();

Console.WriteLine($"Documentos: {documentos}");
Console.WriteLine($"Escritorio: {escritorio}");
Console.WriteLine($"Temporal: {temporal}");

Operaciones básicas con archivos

Verificación de existencia

Antes de realizar operaciones con archivos, es importante verificar su existencia para evitar errores:

using System;
using System.IO;

string rutaArchivo = @"C:\temp\mi_archivo.txt";
string rutaDirectorio = @"C:\temp";

// Verificar existencia de archivo
if (File.Exists(rutaArchivo))
{
    Console.WriteLine("El archivo existe");
    
    // Obtener información básica
    var info = new FileInfo(rutaArchivo);
    Console.WriteLine($"Tamaño: {info.Length} bytes");
    Console.WriteLine($"Creado: {info.CreationTime}");
    Console.WriteLine($"Modificado: {info.LastWriteTime}");
}
else
{
    Console.WriteLine("El archivo no existe");
}

// Verificar existencia de directorio
if (Directory.Exists(rutaDirectorio))
{
    Console.WriteLine("El directorio existe");
}
else
{
    Console.WriteLine("El directorio no existe");
    
    // Crear directorio si no existe
    Directory.CreateDirectory(rutaDirectorio);
    Console.WriteLine("Directorio creado");
}

Información de archivos y directorios

using System;
using System.IO;

string rutaDirectorio = @"C:\temp";

// Obtener archivos en un directorio
string[] archivos = Directory.GetFiles(rutaDirectorio);
Console.WriteLine($"Archivos en {rutaDirectorio}:");
foreach (string archivo in archivos)
{
    Console.WriteLine($"  - {Path.GetFileName(archivo)}");
}

// Obtener archivos con filtro
string[] archivosTxt = Directory.GetFiles(rutaDirectorio, "*.txt");
Console.WriteLine($"\nArchivos .txt:");
foreach (string archivo in archivosTxt)
{
    var info = new FileInfo(archivo);
    Console.WriteLine($"  - {info.Name} ({info.Length} bytes)");
}

// Obtener subdirectorios
string[] directorios = Directory.GetDirectories(rutaDirectorio);
Console.WriteLine($"\nSubdirectorios:");
foreach (string dir in directorios)
{
    Console.WriteLine($"  - {Path.GetFileName(dir)}");
}

Lectura de archivos de texto

Método File.ReadAllText

La forma más simple de leer todo el contenido de un archivo de texto:

using System;
using System.IO;

try
{
    // Leer todo el contenido del archivo
    string rutaArchivo = @"C:\temp\notas.txt";
    string contenido = File.ReadAllText(rutaArchivo);
    
    Console.WriteLine("Contenido del archivo:");
    Console.WriteLine(contenido);
    
    // Información adicional
    Console.WriteLine($"\nLongitud: {contenido.Length} caracteres");
    Console.WriteLine($"Líneas aproximadas: {contenido.Split('\n').Length}");
}
catch (FileNotFoundException)
{
    Console.WriteLine("El archivo no se encontró");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("No tienes permisos para leer el archivo");
}
catch (Exception ex)
{
    Console.WriteLine($"Error al leer el archivo: {ex.Message}");
}

Método File.ReadAllLines

Para leer un archivo línea por línea en un array:

using System;
using System.IO;

try
{
    string rutaArchivo = @"C:\temp\lista_tareas.txt";
    string[] lineas = File.ReadAllLines(rutaArchivo);
    
    Console.WriteLine($"El archivo tiene {lineas.Length} líneas:");
    
    for (int i = 0; i < lineas.Length; i++)
    {
        Console.WriteLine($"{i + 1:D2}: {lineas[i]}");
    }
    
    // Procesar líneas que no estén vacías
    var lineasNoVacias = lineas.Where(linea => !string.IsNullOrWhiteSpace(linea));
    Console.WriteLine($"\nLíneas con contenido: {lineasNoVacias.Count()}");
}
catch (Exception ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

StreamReader para archivos grandes

Para archivos grandes o cuando necesitas más control sobre el proceso de lectura:

using System;
using System.IO;

string rutaArchivo = @"C:\temp\archivo_grande.txt";

try
{
    using (var reader = new StreamReader(rutaArchivo))
    {
        int numeroLinea = 1;
        string linea;
        
        Console.WriteLine("Leyendo archivo línea por línea:");
        
        // Leer línea por línea hasta el final del archivo
        while ((linea = reader.ReadLine()) != null)
        {
            // Procesar cada línea individualmente
            if (!string.IsNullOrWhiteSpace(linea))
            {
                Console.WriteLine($"Línea {numeroLinea}: {linea.Trim()}");
            }
            numeroLinea++;
            
            // Opcional: procesar solo las primeras 10 líneas
            if (numeroLinea > 10)
            {
                Console.WriteLine("... (resto del archivo omitido)");
                break;
            }
        }
        
        Console.WriteLine($"\nArchivo procesado. Total de líneas leídas: {numeroLinea - 1}");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Error al leer el archivo: {ex.Message}");
}

Escritura de archivos de texto

Método File.WriteAllText

Para escribir texto completo en un archivo (sobrescribe el contenido existente):

using System;
using System.IO;

try
{
    string rutaArchivo = @"C:\temp\mi_nota.txt";
    string contenido = @"Esta es mi primera nota.
Creada desde C#.
Con múltiples líneas de texto.";
    
    // Escribir contenido al archivo
    File.WriteAllText(rutaArchivo, contenido);
    
    Console.WriteLine($"Archivo creado exitosamente: {rutaArchivo}");
    Console.WriteLine($"Contenido escrito:\n{contenido}");
    
    // Verificar que se escribió correctamente
    string contenidoLeido = File.ReadAllText(rutaArchivo);
    Console.WriteLine($"\nVerificación - contenido leído:");
    Console.WriteLine(contenidoLeido);
}
catch (Exception ex)
{
    Console.WriteLine($"Error al escribir el archivo: {ex.Message}");
}

Método File.WriteAllLines

Para escribir múltiples líneas desde un array o colección:

using System;
using System.IO;
using System.Collections.Generic;

try
{
    string rutaArchivo = @"C:\temp\lista_compras.txt";
    
    // Lista de elementos para escribir
    var listaCompras = new List<string>
    {
        "Leche",
        "Pan integral",
        "Manzanas",
        "Queso manchego",
        "Huevos frescos",
        "Aceite de oliva"
    };
    
    // Escribir todas las líneas
    File.WriteAllLines(rutaArchivo, listaCompras);
    
    Console.WriteLine($"Lista de compras guardada en: {rutaArchivo}");
    Console.WriteLine("Contenido:");
    
    // Leer y mostrar el contenido
    string[] lineasLeidas = File.ReadAllLines(rutaArchivo);
    for (int i = 0; i < lineasLeidas.Length; i++)
    {
        Console.WriteLine($"{i + 1}. {lineasLeidas[i]}");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

StreamWriter para control avanzado

Para escritura con mayor control y eficiencia:

using System;
using System.IO;

string rutaArchivo = @"C:\temp\registro_actividad.txt";

try
{
    using (var writer = new StreamWriter(rutaArchivo))
    {
        // Escribir encabezado
        writer.WriteLine("=== REGISTRO DE ACTIVIDAD ===");
        writer.WriteLine($"Fecha: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
        writer.WriteLine("================================");
        writer.WriteLine();
        
        // Simular actividades
        for (int i = 1; i <= 5; i++)
        {
            writer.WriteLine($"Actividad {i}: Procesando elemento {i}");
            writer.WriteLine($"  Tiempo: {DateTime.Now:HH:mm:ss}");
            writer.WriteLine($"  Estado: Completado");
            writer.WriteLine();
            
            // Forzar escritura al archivo (flush)
            writer.Flush();
            
            // Simular trabajo
            System.Threading.Thread.Sleep(100);
        }
        
        writer.WriteLine("=== FIN DEL REGISTRO ===");
    }
    
    Console.WriteLine($"Registro creado: {rutaArchivo}");
    
    // Mostrar el contenido creado
    Console.WriteLine("\nContenido del registro:");
    string contenido = File.ReadAllText(rutaArchivo);
    Console.WriteLine(contenido);
}
catch (Exception ex)
{
    Console.WriteLine($"Error al crear el registro: {ex.Message}");
}

Añadir contenido a archivos existentes

File.AppendAllText

Para añadir contenido al final de un archivo existente:

using System;
using System.IO;

string rutaArchivo = @"C:\temp\log_aplicacion.txt";

try
{
    // Crear archivo inicial si no existe
    if (!File.Exists(rutaArchivo))
    {
        File.WriteAllText(rutaArchivo, "=== LOG DE APLICACIÓN ===\n");
    }
    
    // Añadir nuevas entradas al log
    for (int i = 1; i <= 3; i++)
    {
        string entrada = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Evento {i}: Operación completada\n";
        File.AppendAllText(rutaArchivo, entrada);
        
        Console.WriteLine($"Entrada {i} añadida al log");
        System.Threading.Thread.Sleep(1000); // Pausa para mostrar diferentes timestamps
    }
    
    // Mostrar el contenido completo del log
    Console.WriteLine("\n=== CONTENIDO DEL LOG ===");
    string contenidoLog = File.ReadAllText(rutaArchivo);
    Console.WriteLine(contenidoLog);
}
catch (Exception ex)
{
    Console.WriteLine($"Error al trabajar con el log: {ex.Message}");
}

StreamWriter con modo append

using System;
using System.IO;

string rutaArchivo = @"C:\temp\notas_diarias.txt";

try
{
    // Usar StreamWriter en modo append (true)
    using (var writer = new StreamWriter(rutaArchivo, append: true))
    {
        writer.WriteLine($"\n--- Entrada del {DateTime.Now:dd/MM/yyyy} ---");
        writer.WriteLine("Hoy he aprendido sobre lectura y escritura de archivos en C#.");
        writer.WriteLine("Las clases File, StreamReader y StreamWriter son muy útiles.");
        writer.WriteLine("Es importante manejar las excepciones correctamente.");
        writer.WriteLine("--- Fin de la entrada ---");
    }
    
    Console.WriteLine("Nueva entrada añadida al diario");
    
    // Mostrar las últimas líneas del archivo
    string[] lineas = File.ReadAllLines(rutaArchivo);
    Console.WriteLine("\nÚltimas 8 líneas del archivo:");
    
    int inicio = Math.Max(0, lineas.Length - 8);
    for (int i = inicio; i < lineas.Length; i++)
    {
        Console.WriteLine(lineas[i]);
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

Codificación de caracteres

Especificar codificación

using System;
using System.IO;
using System.Text;

string rutaArchivo = @"C:\temp\texto_unicode.txt";

try
{
    // Texto con caracteres especiales
    string textoEspecial = "Texto con acentos: ñáéíóú\nCaracteres especiales: €¿¡";
    
    // Escribir con codificación UTF-8
    File.WriteAllText(rutaArchivo, textoEspecial, Encoding.UTF8);
    Console.WriteLine("Archivo escrito con codificación UTF-8");
    
    // Leer con codificación específica
    string contenidoUTF8 = File.ReadAllText(rutaArchivo, Encoding.UTF8);
    Console.WriteLine($"Contenido leído (UTF-8):\n{contenidoUTF8}");
    
    // Escribir con diferentes codificaciones para comparar
    File.WriteAllText(rutaArchivo + ".ascii", textoEspecial, Encoding.ASCII);
    File.WriteAllText(rutaArchivo + ".unicode", textoEspecial, Encoding.Unicode);
    
    Console.WriteLine("\nArchivos creados con diferentes codificaciones:");
    Console.WriteLine($"- {rutaArchivo} (UTF-8)");
    Console.WriteLine($"- {rutaArchivo}.ascii (ASCII)");
    Console.WriteLine($"- {rutaArchivo}.unicode (Unicode)");
}
catch (Exception ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

Procesamiento de archivos CSV básico

Leer archivo CSV simple

using System;
using System.IO;
using System.Linq;

string rutaCSV = @"C:\temp\empleados.csv";

try
{
    // Crear archivo CSV de ejemplo
    var datosCSV = @"Nombre,Apellido,Edad,Departamento
Juan,Pérez,30,Desarrollo
María,García,25,Marketing
Carlos,López,35,Ventas
Ana,Martínez,28,Desarrollo";

    File.WriteAllText(rutaCSV, datosCSV);
    
    // Leer y procesar el CSV
    string[] lineas = File.ReadAllLines(rutaCSV);
    
    // Primera línea son los encabezados
    string[] encabezados = lineas[0].Split(',');
    Console.WriteLine("Encabezados: " + string.Join(" | ", encabezados));
    Console.WriteLine(new string('-', 50));
    
    // Procesar datos línea por línea
    for (int i = 1; i < lineas.Length; i++)
    {
        string[] campos = lineas[i].Split(',');
        
        if (campos.Length == encabezados.Length)
        {
            Console.WriteLine($"Empleado {i}:");
            for (int j = 0; j < encabezados.Length; j++)
            {
                Console.WriteLine($"  {encabezados[j]}: {campos[j]}");
            }
            Console.WriteLine();
        }
    }
    
    // Estadísticas básicas
    var empleadosDesarrollo = lineas.Skip(1)
        .Where(linea => linea.Contains("Desarrollo"))
        .Count();
    
    Console.WriteLine($"Empleados en Desarrollo: {empleadosDesarrollo}");
}
catch (Exception ex)
{
    Console.WriteLine($"Error al procesar CSV: {ex.Message}");
}

Buenas prácticas y manejo de errores

Uso de using statements

using System;
using System.IO;

// Ejemplo de buenas prácticas
static void ProcesarArchivoSeguro(string rutaArchivo)
{
    try
    {
        // Verificar existencia antes de procesar
        if (!File.Exists(rutaArchivo))
        {
            Console.WriteLine($"El archivo {rutaArchivo} no existe");
            return;
        }
        
        // Usar using para garantizar liberación de recursos
        using (var reader = new StreamReader(rutaArchivo))
        {
            string linea;
            int contador = 0;
            
            while ((linea = reader.ReadLine()) != null)
            {
                contador++;
                
                // Procesar línea de manera segura
                if (!string.IsNullOrWhiteSpace(linea))
                {
                    Console.WriteLine($"Línea {contador}: {linea.Trim()}");
                }
                
                // Evitar archivos excesivamente grandes
                if (contador > 1000)
                {
                    Console.WriteLine("Archivo demasiado grande, deteniendo lectura");
                    break;
                }
            }
        }
        // El StreamReader se cierra automáticamente aquí
    }
    catch (IOException ex)
    {
        Console.WriteLine($"Error de E/S: {ex.Message}");
    }
    catch (UnauthorizedAccessException)
    {
        Console.WriteLine("No tienes permisos para acceder al archivo");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error inesperado: {ex.Message}");
    }
}

// Ejemplo de uso
string archivo = @"C:\temp\datos.txt";
ProcesarArchivoSeguro(archivo);

Respaldo y recuperación

using System;
using System.IO;

static void EscribirConRespaldo(string rutaArchivo, string contenido)
{
    string rutaRespaldo = rutaArchivo + ".backup";
    
    try
    {
        // Crear respaldo si el archivo existe
        if (File.Exists(rutaArchivo))
        {
            File.Copy(rutaArchivo, rutaRespaldo, overwrite: true);
            Console.WriteLine($"Respaldo creado: {rutaRespaldo}");
        }
        
        // Escribir nuevo contenido
        File.WriteAllText(rutaArchivo, contenido);
        Console.WriteLine($"Archivo actualizado: {rutaArchivo}");
        
        // Si todo sale bien, eliminar respaldo (opcional)
        if (File.Exists(rutaRespaldo))
        {
            File.Delete(rutaRespaldo);
            Console.WriteLine("Respaldo eliminado (operación exitosa)");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error al escribir archivo: {ex.Message}");
        
        // Restaurar desde respaldo si existe
        if (File.Exists(rutaRespaldo))
        {
            try
            {
                File.Copy(rutaRespaldo, rutaArchivo, overwrite: true);
                Console.WriteLine("Archivo restaurado desde respaldo");
            }
            catch
            {
                Console.WriteLine("No se pudo restaurar el respaldo");
            }
        }
    }
}

// Ejemplo de uso
string rutaArchivo = @"C:\temp\documento_importante.txt";
string nuevoContenido = "Este es el nuevo contenido del documento.";
EscribirConRespaldo(rutaArchivo, nuevoContenido);

Resumen

El manejo de archivos de texto en C# es una habilidad fundamental que abre numerosas posibilidades para el almacenamiento y procesamiento de datos. Las clases File, StreamReader y StreamWriter proporcionan métodos versátiles para leer y escribir archivos, desde operaciones simples hasta procesamiento avanzado de grandes volúmenes de datos.

Es crucial recordar la importancia del manejo adecuado de errores, el uso de using statements para garantizar la liberación de recursos, y la consideración de aspectos como la codificación de caracteres. Estas prácticas aseguran que nuestras aplicaciones sean robustas y confiables al trabajar con el sistema de archivos. Con estos conocimientos, estás preparado para implementar funcionalidades de persistencia de datos y procesamiento de archivos en tus aplicaciones C#.