1. Introducción: ¿por qué los archivos se ponen "difíciles"?
A veces abres un documento y de repente el sistema dice: "Archivo no encontrado". O un intento de guardar termina con: "Acceso denegado". Estos son justo los casos donde algo raro pasa con los archivos. Si con las codificaciones lidiamos con "caracteres raros", aquí los problemas ya vienen de la propia filesystem.
Puedes imaginar la filesystem como una gran biblioteca, y la aplicación como el bibliotecario. Cuando pide un archivo, puede recibir distintas respuestas:
- El libro (es decir, el archivo) no existe — simplemente no está.
- La sección de la biblioteca donde debería estar el libro no existe — falta el directorio necesario.
- El libro está "cerrado" o prestado — el archivo está usado por otro proceso o no tienes permisos de acceso.
- La estantería está llena — no hay espacio en disco para crear un nuevo archivo.
- O, por ejemplo, intentas poner el libro en la sección de devoluciones de DVD — es decir, la operación no está soportada.
En C# todas estas situaciones se representan mediante excepciones. Y la tarea del desarrollador no es simplemente ver cómo la aplicación "se cae", sino anticipar problemas posibles y manejarlos con cuidado. A nadie le gusta que el usuario vea un misterioso mensaje de error lleno de texto incomprensible.
Ya conocemos la construcción try-catch. Es nuestro salvavidas, que permite "capturar" una excepción y tomar acciones en lugar de dejar que la app termine abruptamente.
// Este es nuestro viejo conocido, recordatorio de la Lección 57
try
{
// Aquí escribimos código que puede lanzar un error
// Por ejemplo, intento de lectura de un archivo
}
catch (Exception ex) // Capturamos cualquier excepción
{
// Aquí manejamos el error
Console.WriteLine($"Uy, ocurrió un error: {ex.Message}");
}
Hoy nos vamos a profundizar en excepciones específicas que aparecen al trabajar con archivos. Esto nos permitirá escribir código más robusto, que sepa "hablar" con la filesystem incluso cuando esta se pone "caprichosa".
¡Las excepciones no siempre son bugs, son señales de socorro!
Es importante entender: una excepción no siempre significa que tu código está mal. A menudo es una señal de que algo salió mal en el entorno externo con el que interactúa tu código. La filesystem es un buen ejemplo de ese entorno externo. Puedes escribir un código perfectamente correcto para leer un archivo, pero si el usuario borró ese archivo antes de que tu programa lo leyera, obtendrás una excepción. ¡Y eso es normal! Tu tarea como desarrollador es enseñar a la aplicación a reaccionar ante esas situaciones.
Veamos las señales de socorro más comunes que puedes encontrar al trabajar con archivos.
2. FileNotFoundException: el archivo simplemente no estaba
Esta es, quizá, la excepción más común cuando trabajas con archivos. Ocurre cuando intentas abrir, leer o realizar otra operación sobre un archivo que no existe en la ruta indicada.
Un ejemplo de la vida real: le pides a un amigo que te traiga el libro "Programación en C# 14 para principiantes" de su biblioteca, y él responde: "No tengo ese libro". De la misma manera tu programa puede pedir al sistema operativo: "Dame el archivo settings.txt", y el sistema responderá: "Lo siento, no existe".
Vamos a escribir código que lee el archivo abracadabra.txt para nuestra app de gestión de tareas. Si no existe, debemos informar al usuario en vez de simplemente "caer".
try
{
using var reader = new StreamReader("abracadabra.txt");
Console.WriteLine(reader.ReadToEnd());
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Archivo no encontrado: " + ex.FileName);
}
En la vida cotidiana es como llegar a la parada y que no venga ni el autobús ni la lanzadera. Triste.
Nota: A menudo esta excepción va acompañada de una ruta incorrecta (por ejemplo, olvidaste que trabajas desde otro directorio).
3. DirectoryNotFoundException: las carpetas se evaporaron
Esta excepción es muy parecida a FileNotFoundException, pero afecta no al archivo en sí, sino al directorio (carpeta) donde debería estar. Si pasas una ruta como "C:\MyDocuments\MyProject\Data\report.txt", y la carpeta Data no existe, obtendrás DirectoryNotFoundException.
En nuestra app, si quisiéramos guardar configuración en una subcarpeta data, por ejemplo "./data/app_settings.txt", y esa carpeta no existiera, al intentar escribir o leer nos enfrentaríamos a este problema.
DirectoryNotFoundException puede atraparse por separado, igual que FileNotFoundException, o formar parte de una IOException más general, de la que hablaremos después.
try
{
using var writer = new StreamWriter(@"C:\very\strange\path\file.txt");
writer.WriteLine("Hello world");
}
catch (DirectoryNotFoundException ex)
{
Console.WriteLine("¡Directorio no encontrado!");
}
Error común: La carpeta puede borrarse en cualquier momento (por ejemplo, alguien limpia archivos temporales), o tienes mal configurada la ruta de escritura.
4. UnauthorizedAccessException: ¡entrada prohibida!
Imagina que quieres dejar un libro en una estantería donde hay un cartel que dice "Acceso solo para personal". Eso es exactamente UnauthorizedAccessException! Ocurre cuando tu programa no tiene los permisos necesarios para un archivo o directorio. Puede deberse a:
- Permisos insuficientes del usuario: Intentas escribir en una carpeta donde solo puede escribir un administrador (por ejemplo, C:\Windows).
- Archivo marcado como "solo lectura": Intentas modificar un archivo con el atributo de solo lectura.
- Archivo es del sistema o está oculto: Y tiene restricciones específicas.
Esto es muy común en entornos corporativos o cuando los usuarios instalan programas en directorios protegidos.
Probemos a escribir un archivo en una carpeta del sistema donde un usuario normal no tiene permisos. (Atención: ejecuta este código con cuidado para no ensuciar carpetas del sistema, o en una "sandbox").
try
{
using var writer = new StreamWriter("/system/settings.conf");
writer.WriteLine("¡Todo el poder para los estudiantes!");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("¡Sin acceso al archivo o directorio!");
}
Si ejecutas este código sin privilegios de administrador, probablemente verás el mensaje "Acceso a esta carpeta denegado". Si lo ejecutas como administrador, lo más probable es que el archivo se cree. Este ejemplo muestra lo importante que es manejar UnauthorizedAccessException para que el usuario entienda por qué la app no pudo completar la operación.
5. IOException: el "ups" universal de la filesystem
IOException es la excepción más general relacionada con operaciones de I/O. Se lanza cuando hay algún problema con el dispositivo de entrada/salida o con la filesystem que no encaja en excepciones más específicas como FileNotFoundException o UnauthorizedAccessException.
Escenarios típicos donde puedes obtener IOException:
- El archivo ya está en uso por otro programa: Por ejemplo, intentas borrar un archivo abierto en Notepad u otra instancia de tu app.
- Disco lleno: No hay suficiente espacio para escribir el archivo.
- Archivo o filesystem corrupto: Raro, pero puede pasar.
- Problemas de red: Si el archivo está en un recurso de red y la conexión se corta.
- Nombre de archivo o ruta demasiado larga. (Esto puede ser también PathTooLongException, pero a veces cae bajo IOException).
IOException es una especie de "llave maestra" para muchos problemas. Cuando capturas IOException, suele ser útil mirar su propiedad Message para obtener más detalle sobre qué exactamente falló.
try
{
using var file = new FileStream("busyfile.txt", FileMode.Open, FileAccess.ReadWrite, FileShare.None);
// mantenemos el archivo abierto por alguna razón
// al mismo tiempo en otro lugar:
using var writer = new StreamWriter("busyfile.txt");
writer.WriteLine("Intento de escritura...");
}
catch (IOException ex)
{
Console.WriteLine("Error de entrada/salida: " + ex.Message);
}
Punto importante: IOException es la clase base para muchas otras excepciones relacionadas con archivos.
6. Otras "molestias" pero también importantes
PathTooLongException
Es un problema menos común, pero ocurre: tu ruta (o nombre de archivo/carpeta) es demasiado larga para el sistema operativo. Por ejemplo, si decides poner en el nombre del archivo un breve resumen de "Guerra y paz", Windows no te lo agradecerá.
En Windows el límite histórico es de 260 caracteres para la ruta completa. Las versiones nuevas del OS y .NET permiten rutas largas, pero no siempre está habilitado por defecto.
try
{
string veryLongPath = new string('a', 300); // ¡300 caracteres!
using var writer = new StreamWriter(veryLongPath + ".txt");
writer.WriteLine("¡Este nombre de archivo es demasiado largo!");
}
catch (PathTooLongException ex)
{
Console.WriteLine("¡Nombre de archivo o ruta demasiado larga!");
}
NotSupportedException
Es un caso raro pero "surrealista" cuando pasas al constructor de StreamReader o FileStream una cadena de ruta inválida, por ejemplo con caracteres prohibidos o usas rutas "mágicas" tipo C:::\wow???\file.txt.
7. Matices útiles
Qué excepción corresponde a qué
| Excepción | Motivo | Ejemplo de situación |
|---|---|---|
|
Archivo no encontrado | Abrir un archivo inexistente |
|
Directorio no encontrado | Abrir un archivo en una carpeta borrada |
|
Sin permisos | Escribir en un directorio protegido |
|
Error general de I/O | Archivo abierto por otro proceso |
|
Ruta demasiado larga | Nombre de archivo/carpeta excesivamente largo |
|
Formato de ruta no soportado | Ruta con caracteres prohibidos |
Errores frecuentes y casos especiales
Ejemplo: Archivo ocupado por otro proceso
Imagina que, como un verdadero hacker, abriste un archivo de texto en Notepad y lo olvidaste abierto. En ese momento tu app intenta escribir en el mismo archivo. Aquí te llega una IOException (o incluso una "sharing violation").
Ejemplo: Falta de acceso
Intenta guardar datos en C:\Windows sin privilegios administrativos — obtendrás UnauthorizedAccessException. Lo mismo pasa si abriste un archivo solo para lectura y tratas de escribir en él.
Ejemplo: Ruta inválida
En Windows no puedes nombrar archivos con los caracteres <>:"/\|?*. Si intentas escribir un archivo así, la app lanzará NotSupportedException (o ArgumentException).
Ejemplo: No hay espacio en disco
Curiosamente, esto también lanza IOException — por ejemplo cuando el disco está lleno (por eso es útil recordar limpiar la carpeta Downloads de vez en cuando).
Cómo no meter la pata: mejores prácticas para detectar errores
- Comprueba la existencia del archivo con File.Exists y la del directorio con Directory.Exists antes de intentar abrir el archivo. Pero ten cuidado: el archivo puede desaparecer o aparecer después de la comprobación (clásica race condition).
- Nunca "tragues" excepciones completamente (no hagas simplemente catch { }), salvo que estés escribiendo un logger muy estricto. Al menos registra o muestra al usuario qué pasó.
- Procura capturar excepciones concretas (FileNotFoundException, DirectoryNotFoundException) y no solo la general Exception.
- Para apps multiplataforma ten en cuenta que permisos, formatos de ruta y límites de longitud difieren entre Windows, Linux y macOS.
- Si trabajas con archivos de texto, especifica siempre la codificación explícitamente — así evitas sorpresas.
- Para operaciones masivas con archivos es recomendable usar procesamiento "bulk" teniendo en cuenta fallos posibles en cada paso.
Resumen rápido de excepciones típicas
| Excepción | ¿Cuándo ocurre? | ¿Cómo prevenir/manejar? |
|---|---|---|
|
No hay archivo en la ruta indicada | Comprueba con File.Exists o créalo |
|
La ruta contiene un directorio inexistente | Verifica la ruta, crea directorios con Directory.CreateDirectory |
|
Sin permisos para el archivo/carpeta, archivo de solo lectura, ocupado por otro proceso | Ejecuta con el usuario correcto, revisa ACL, cierra archivos correctamente |
|
Error general de I/O, archivo ocupado, disco lleno | Usa try-catch, evita dejar archivos abiertos |
|
Ruta o nombre de archivo demasiado largo | Acorta la ruta, usa rutas relativas |
|
Formato de ruta inválido | Valida la cadena de ruta contra caracteres prohibidos |
Diagrama de flujo "¿Qué hacer ante un error con un archivo?"
flowchart TD
A[Operación con archivo] --> B{¿Se lanzó una excepción?}
B -- No --> C[Operación completada con éxito]
B -- Sí --> D{¿Qué tipo de excepción?}
D -- FileNotFound --> E[Pide al usuario indicar el archivo correcto o créalo]
D -- DirectoryNotFound --> F[Crea el directorio faltante]
D -- UnauthorizedAccess --> G[Pide al usuario reiniciar con permisos adecuados]
D -- IOException --> H[Verifica quién tiene el archivo abierto, revisa el disco]
D -- PathTooLong --> I[Acorta la ruta]
D -- NotSupported --> J[Revisa el formato de la ruta]
D -- Other --> K[Muestra un mensaje y revisa los logs]
8. ¿Cómo se ven estas excepciones en una aplicación real?
Desarrollando nuestro proyecto de ejemplo, supongamos que tenemos un mini-programa que guarda notas del usuario en un archivo y luego las lee.
string notesPath = "notes.txt";
Console.Write("Introduce una nota: ");
string note = Console.ReadLine();
try
{
// Guardamos la nota
using var writer = new StreamWriter(notesPath, true, Encoding.UTF8);
writer.WriteLine(note);
// Leemos todas las notas
Console.WriteLine("Tus notas:");
using var reader = new StreamReader(notesPath, Encoding.UTF8);
Console.WriteLine(reader.ReadToEnd());
}
catch (FileNotFoundException)
{
Console.WriteLine("Archivo de notas no encontrado. Intenta crearlo manualmente.");
}
catch (DirectoryNotFoundException)
{
Console.WriteLine("La ruta del archivo de notas es inválida. Comprueba que la carpeta existe.");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("No tienes permisos para escribir o leer el archivo. Ejecuta el programa como administrador.");
}
catch (IOException ex)
{
Console.WriteLine("Ocurrió un error de entrada/salida: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Error inesperado: " + ex.Message);
}
En este ejemplo se muestra una situación típica: intentar guardar datos en un archivo y luego leerlos. Cada bloque catch maneja una clase concreta de errores — desde la ausencia del archivo o carpeta hasta problemas de permisos y errores generales de I/O. Gracias a esto la app no "se cae" ante el primer fallo, sino que informa al usuario qué pasó y qué se puede hacer. Este enfoque hace la aplicación más fiable y amigable.
GO TO FULL VERSION