Módulo 10: Ficheros y entrada/salida

Objetivos del módulo

  • Leer y escribir archivos de texto
  • Trabajar con rutas y directorios
  • Usar streams para archivos grandes
  • Serializar y deserializar JSON con System.Text.Json
  • Aplicar using para gestión segura de recursos

1. Leer y escribir archivos de texto

Métodos simples de File

La clase File (de System.IO) ofrece métodos estáticos rápidos para operaciones sencillas:

// Escribir un archivo completo
File.WriteAllText("saludo.txt", "Hola, mundo!\nBienvenido al curso.");

// Leer un archivo completo
string contenido = File.ReadAllText("saludo.txt");
Console.WriteLine(contenido);

// Escribir líneas (un array de strings)
string[] lineas = { "Primera línea", "Segunda línea", "Tercera línea" };
File.WriteAllLines("lineas.txt", lineas);

// Leer líneas (devuelve un array)
string[] lineasLeidas = File.ReadAllLines("lineas.txt");
foreach (string linea in lineasLeidas)
{
    Console.WriteLine(linea);
}

// Añadir texto al final (no sobreescribe)
File.AppendAllText("saludo.txt", "\nEsta línea se añade al final.");
File.AppendAllLines("lineas.txt", new[] { "Línea añadida" });
Método Acción
WriteAllText(ruta, texto) Escribe (sobreescribe) texto completo
ReadAllText(ruta) Lee todo el archivo como un string
WriteAllLines(ruta, líneas) Escribe un array de líneas
ReadAllLines(ruta) Lee todas las líneas como array
AppendAllText(ruta, texto) Añade texto al final
AppendAllLines(ruta, líneas) Añade líneas al final
Exists(ruta) Comprueba si el archivo existe
Delete(ruta) Elimina el archivo
Copy(origen, destino) Copia un archivo
Move(origen, destino) Mueve/renombra un archivo

2. Comprobar antes de actuar

string ruta = "datos.txt";

if (File.Exists(ruta))
{
    string contenido = File.ReadAllText(ruta);
    Console.WriteLine(contenido);
}
else
{
    Console.WriteLine($"El archivo '{ruta}' no existe.");
}

3. Trabajar con rutas: Path

La clase Path ayuda a construir y manipular rutas de archivo de forma segura:

string ruta = @"C:\Users\Ana\Documentos\datos.txt";

Console.WriteLine(Path.GetFileName(ruta));        // datos.txt
Console.WriteLine(Path.GetFileNameWithoutExtension(ruta)); // datos
Console.WriteLine(Path.GetExtension(ruta));       // .txt
Console.WriteLine(Path.GetDirectoryName(ruta));   // C:\Users\Ana\Documentos

// Combinar rutas (forma segura)
string carpeta = @"C:\Users\Ana";
string archivo = "datos.txt";
string rutaCompleta = Path.Combine(carpeta, "Documentos", archivo);
Console.WriteLine(rutaCompleta);  // C:\Users\Ana\Documentos\datos.txt

// Ruta temporal
string temp = Path.GetTempFileName();
Console.WriteLine(temp);  // C:\Users\Ana\AppData\Local\Temp\tmp1234.tmp

💡 Consejo: Siempre usa Path.Combine() para construir rutas. Nunca concatenes strings con + porque puedes olvidar separadores.


4. Trabajar con directorios: Directory

string carpeta = "MisArchivos";

// Crear directorio
if (!Directory.Exists(carpeta))
{
    Directory.CreateDirectory(carpeta);
    Console.WriteLine($"Carpeta '{carpeta}' creada.");
}

// Listar archivos
string[] archivos = Directory.GetFiles(carpeta);
foreach (string archivo in archivos)
{
    Console.WriteLine(archivo);
}

// Listar archivos con filtro
string[] textos = Directory.GetFiles(carpeta, "*.txt");
string[] todos = Directory.GetFiles(carpeta, "*.*", SearchOption.AllDirectories);

// Listar subcarpetas
string[] subcarpetas = Directory.GetDirectories(carpeta);

// Obtener directorio actual
string actual = Directory.GetCurrentDirectory();
Console.WriteLine($"Directorio actual: {actual}");

5. Streams: lectura y escritura eficiente

📘 Concepto: Los streams (flujos) permiten leer y escribir datos poco a poco, en lugar de cargar todo en memoria. Son esenciales para archivos grandes.

StreamWriter: escribir

// Opción 1: using declaration (recomendado en C# moderno)
using StreamWriter writer = new StreamWriter("registro.txt");
writer.WriteLine("Línea 1");
writer.WriteLine("Línea 2");
writer.WriteLine($"Fecha: {DateTime.Now}");
// El archivo se cierra automáticamente al salir del ámbito

// Opción 2: using block (clásico)
using (StreamWriter writer2 = new StreamWriter("registro2.txt", append: true))
{
    writer2.WriteLine("Texto añadido al final");
}
// writer2 se cierra aquí automáticamente

StreamReader: leer

using StreamReader reader = new StreamReader("registro.txt");

// Leer línea por línea (eficiente para archivos grandes)
string? linea;
int numLinea = 1;

while ((linea = reader.ReadLine()) is not null)
{
    Console.WriteLine($"{numLinea}: {linea}");
    numLinea++;
}

📘 Concepto: using asegura que el stream se cierra siempre, incluso si ocurre una excepción. Es equivalente a usar try-finally con Close().


6. JSON con System.Text.Json

JSON (JavaScript Object Notation) es el formato estándar para intercambiar datos. C# incluye System.Text.Json para serializarlo y deserializarlo.

Serializar: objeto → JSON

using System.Text.Json;

// Definir una clase
class Producto
{
    public string Nombre { get; set; } = "";
    public double Precio { get; set; }
    public int Stock { get; set; }
    public bool Disponible { get; set; }
}

// Crear un objeto
var producto = new Producto
{
    Nombre = "Teclado mecánico",
    Precio = 89.99,
    Stock = 150,
    Disponible = true
};

// Serializar a JSON
var opciones = new JsonSerializerOptions
{
    WriteIndented = true  // JSON bonito, con indentación
};

string json = JsonSerializer.Serialize(producto, opciones);
Console.WriteLine(json);
// {
//   "Nombre": "Teclado mec\u00E1nico",
//   "Precio": 89.99,
//   "Stock": 150,
//   "Disponible": true
// }

// Guardar JSON en archivo
File.WriteAllText("producto.json", json);

Deserializar: JSON → objeto

// Leer JSON de archivo
string jsonArchivo = File.ReadAllText("producto.json");

// Deserializar a objeto
Producto? prod = JsonSerializer.Deserialize<Producto>(jsonArchivo);

if (prod is not null)
{
    Console.WriteLine($"Producto: {prod.Nombre}");
    Console.WriteLine($"Precio: {prod.Precio:C2}");
}

Listas de objetos

var productos = new List<Producto>
{
    new() { Nombre = "Ratón", Precio = 29.99, Stock = 200, Disponible = true },
    new() { Nombre = "Monitor", Precio = 299.00, Stock = 50, Disponible = true },
    new() { Nombre = "Webcam", Precio = 59.50, Stock = 0, Disponible = false }
};

// Serializar lista
string jsonLista = JsonSerializer.Serialize(productos, opciones);
File.WriteAllText("productos.json", jsonLista);

// Deserializar lista
string jsonLeido = File.ReadAllText("productos.json");
List<Producto>? lista = JsonSerializer.Deserialize<List<Producto>>(jsonLeido);

if (lista is not null)
{
    foreach (var p in lista)
    {
        Console.WriteLine($"{p.Nombre}: {p.Precio:C2} ({p.Stock} uds)");
    }
}

Opciones de serialización

var opciones = new JsonSerializerOptions
{
    WriteIndented = true,                                    // Indentación
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,      // camelCase en JSON
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,  // Ignorar nulls
    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping  // Acentos sin escapar
};

7. Información de archivos: FileInfo

FileInfo info = new FileInfo("producto.json");

if (info.Exists)
{
    Console.WriteLine($"Nombre: {info.Name}");
    Console.WriteLine($"Tamaño: {info.Length} bytes");
    Console.WriteLine($"Creado: {info.CreationTime}");
    Console.WriteLine($"Modificado: {info.LastWriteTime}");
    Console.WriteLine($"Extensión: {info.Extension}");
    Console.WriteLine($"Directorio: {info.DirectoryName}");
    Console.WriteLine($"Solo lectura: {info.IsReadOnly}");
}

8. Ejemplo práctico: agenda de contactos en JSON

using System.Text.Json;

class Contacto
{
    public string Nombre { get; set; } = "";
    public string Telefono { get; set; } = "";
    public string Email { get; set; } = "";
}

const string ARCHIVO = "contactos.json";

var opciones = new JsonSerializerOptions
{
    WriteIndented = true,
    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};

// Cargar contactos existentes o crear lista vacía
List<Contacto> contactos;
if (File.Exists(ARCHIVO))
{
    string json = File.ReadAllText(ARCHIVO);
    contactos = JsonSerializer.Deserialize<List<Contacto>>(json) ?? new();
}
else
{
    contactos = new();
}

bool salir = false;
while (!salir)
{
    Console.WriteLine("\n--- AGENDA ---");
    Console.WriteLine("1. Ver contactos");
    Console.WriteLine("2. Añadir contacto");
    Console.WriteLine("3. Buscar contacto");
    Console.WriteLine("4. Salir");
    Console.Write("Opción: ");

    switch (Console.ReadLine())
    {
        case "1":
            if (contactos.Count == 0)
            {
                Console.WriteLine("No hay contactos.");
            }
            else
            {
                for (int i = 0; i < contactos.Count; i++)
                {
                    var c = contactos[i];
                    Console.WriteLine($"  {i + 1}. {c.Nombre} | {c.Telefono} | {c.Email}");
                }
            }
            break;

        case "2":
            Console.Write("Nombre: ");
            string nombre = Console.ReadLine() ?? "";
            Console.Write("Teléfono: ");
            string telefono = Console.ReadLine() ?? "";
            Console.Write("Email: ");
            string email = Console.ReadLine() ?? "";

            contactos.Add(new Contacto { Nombre = nombre, Telefono = telefono, Email = email });

            // Guardar en archivo
            string jsonGuardar = JsonSerializer.Serialize(contactos, opciones);
            File.WriteAllText(ARCHIVO, jsonGuardar);
            Console.WriteLine("Contacto guardado.");
            break;

        case "3":
            Console.Write("Buscar: ");
            string buscar = Console.ReadLine() ?? "";
            var encontrados = contactos.FindAll(c =>
                c.Nombre.Contains(buscar, StringComparison.OrdinalIgnoreCase));

            Console.WriteLine($"Se encontraron {encontrados.Count} resultado(s):");
            foreach (var c in encontrados)
            {
                Console.WriteLine($"  {c.Nombre} | {c.Telefono} | {c.Email}");
            }
            break;

        case "4":
            salir = true;
            break;
    }
}

9. Ejercicios

Ejercicio 1: Registro de notas

Crea un programa que gestione las notas de alumnos en un archivo de texto. Cada línea: nombre;nota1;nota2;nota3. Permite: añadir alumno, ver todos, calcular medias, encontrar mejor/peor alumno.

Ejercicio 2: Gestor de tareas JSON

Crea un gestor de tareas (To-Do) que almacene en JSON: título, descripción, fecha de creación, completada (sí/no), prioridad (enum). Permite: añadir, listar, completar, eliminar, filtrar por estado/prioridad.

Ejercicio 3: Analizador de logs

Lee un archivo de log con formato [FECHA] [NIVEL] Mensaje (niveles: INFO, WARNING, ERROR). Muestra: total de entradas por nivel, todos los errores, estadísticas por día.

Ejercicio 4: Copia de seguridad

Crea una herramienta que copie todos los archivos de una carpeta a otra (carpeta backup), añadiendo la fecha al nombre. Si la carpeta destino no existe, la crea. Muestra un resumen final.


Resumen

Concepto Método / Clase
Leer texto File.ReadAllText(), File.ReadAllLines()
Escribir texto File.WriteAllText(), File.WriteAllLines()
Añadir texto File.AppendAllText()
Comprobar existencia File.Exists(), Directory.Exists()
Crear directorio Directory.CreateDirectory()
Rutas seguras Path.Combine(), Path.GetFileName()
Stream escritura StreamWriter + using
Stream lectura StreamReader + using
Serializar JSON JsonSerializer.Serialize(obj)
Deserializar JSON JsonSerializer.Deserialize<T>(json)
Info de archivo FileInfo