Módulo 09: Manejo de errores
Objetivos del módulo
- Comprender qué son las excepciones y por qué ocurren
- Dominar
try,catch,finally - Crear excepciones personalizadas
- Aplicar buenas prácticas en el manejo de errores
Depuración de excepciones en Visual Studio Community
Visual Studio Community incluye un depurador muy potente que te ayuda a inspeccionar excepciones paso a paso. Usa F9 para colocar puntos de interrupción (breakpoints) y F10/F11 para recorrer el código mientras inspeccionas el valor de las variables:
Panel de depuración de Visual Studio: breakpoints, variables locales y configuración de excepciones
💡 Consejo: Abre Depurar → Ventanas → Configuración de excepciones (
Ctrl+Alt+E) para indicar a VS que se detenga automáticamente cuando se lance un tipo concreto de excepción, incluso si está dentro de uncatch.
1. ¿Qué es una excepción?
📘 Concepto: Una excepción es un error que ocurre durante la ejecución del programa (no al compilar). Cuando ocurre una excepción que no está controlada, el programa se detiene abruptamente.
Ejemplos comunes
// DivideByZeroException
int resultado = 10 / 0;
// FormatException
int numero = int.Parse("abc");
// IndexOutOfRangeException
int[] arr = { 1, 2, 3 };
Console.WriteLine(arr[10]);
// NullReferenceException
string? texto = null;
Console.WriteLine(texto.Length);
// FileNotFoundException
string contenido = File.ReadAllText("archivo_inexistente.txt");
Si no capturas estas excepciones, el programa se cierra mostrando un mensaje de error al usuario.
2. try / catch: capturar excepciones
La estructura try-catch permite capturar una excepción y manejarla en lugar de que el programa se cierre:
try
{
Console.Write("Introduce un número: ");
int numero = int.Parse(Console.ReadLine()!);
int resultado = 100 / numero;
Console.WriteLine($"100 / {numero} = {resultado}");
}
catch (FormatException)
{
Console.WriteLine("Error: no has introducido un número válido.");
}
catch (DivideByZeroException)
{
Console.WriteLine("Error: no se puede dividir entre cero.");
}
| Parte | Función |
|---|---|
try { } | Contiene el código que podría lanzar una excepción |
catch (TipoExcepcion) { } | Se ejecuta solo si ocurre una excepción de ese tipo |
Capturar la información de la excepción
try
{
int[] nums = { 1, 2, 3 };
Console.WriteLine(nums[10]);
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine($"Error: {ex.Message}");
// "Error: Index was outside the bounds of the array."
}
Capturar cualquier excepción
try
{
// código peligroso
}
catch (Exception ex) // Exception es la clase base de todas las excepciones
{
Console.WriteLine($"Ha ocurrido un error: {ex.Message}");
}
⚠️ Importante: Capturar
Exception(la clase base) captura cualquier excepción. Úsalo solo como último recurso. Es mejor capturar excepciones específicas primero.
Orden de los catch
Los bloques catch se evalúan de más específico a más general:
try
{
// código
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"Archivo no encontrado: {ex.FileName}");
}
catch (IOException ex)
{
Console.WriteLine($"Error de entrada/salida: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Error inesperado: {ex.Message}");
}
3. finally: código que siempre se ejecuta
📘 Concepto: El bloque
finallyse ejecuta siempre, tanto si hubo excepción como si no. Es ideal para liberar recursos (cerrar archivos, conexiones, etc.).
StreamReader? lector = null;
try
{
lector = new StreamReader("datos.txt");
string contenido = lector.ReadToEnd();
Console.WriteLine(contenido);
}
catch (FileNotFoundException)
{
Console.WriteLine("El archivo no existe.");
}
catch (IOException ex)
{
Console.WriteLine($"Error al leer: {ex.Message}");
}
finally
{
// Se ejecuta SIEMPRE
lector?.Close(); // ?. → solo llama a Close si lector no es null
Console.WriteLine("Recurso liberado.");
}
4. Lanzar excepciones: throw
Puedes lanzar tus propias excepciones cuando detectas un estado inválido:
static double CalcularIMC(double peso, double altura)
{
if (peso <= 0)
throw new ArgumentException("El peso debe ser mayor que 0", nameof(peso));
if (altura <= 0)
throw new ArgumentException("La altura debe ser mayor que 0", nameof(altura));
return peso / (altura * altura);
}
try
{
double imc = CalcularIMC(70, 0);
}
catch (ArgumentException ex)
{
Console.WriteLine($"Datos inválidos: {ex.Message}");
// "Datos inválidos: La altura debe ser mayor que 0 (Parameter 'altura')"
}
Re-lanzar una excepción
A veces quieres registrar el error y después re-lanzarlo para que el código superior lo maneje:
try
{
// operación riesgosa
}
catch (Exception ex)
{
Console.WriteLine($"Log: {ex.Message}"); // Registrar el error
throw; // Re-lanzar la misma excepción (conserva la traza)
}
⚠️ Importante: Usa
throw;(sin especificar la excepción) para re-lanzar. Si usasthrow ex;se pierde la traza de pila original, dificultando la depuración.
5. Excepciones personalizadas
Para errores específicos de tu aplicación, puedes crear tus propias excepciones:
// Excepción personalizada
class SaldoInsuficienteException : Exception
{
public double SaldoActual { get; }
public double MontoSolicitado { get; }
public SaldoInsuficienteException(double saldo, double monto)
: base($"Saldo insuficiente. Tienes {saldo:C2} y necesitas {monto:C2}")
{
SaldoActual = saldo;
MontoSolicitado = monto;
}
}
// Uso
class CuentaBancaria
{
public double Saldo { get; private set; }
public CuentaBancaria(double saldoInicial)
{
Saldo = saldoInicial;
}
public void Retirar(double monto)
{
if (monto <= 0)
throw new ArgumentException("El monto debe ser positivo");
if (monto > Saldo)
throw new SaldoInsuficienteException(Saldo, monto);
Saldo -= monto;
Console.WriteLine($"Retirado: {monto:C2}. Saldo: {Saldo:C2}");
}
}
// Programa
var cuenta = new CuentaBancaria(100);
try
{
cuenta.Retirar(50); // OK
cuenta.Retirar(80); // Error: solo tiene 50
}
catch (SaldoInsuficienteException ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine($"Te faltan: {ex.MontoSolicitado - ex.SaldoActual:C2}");
}
6. Jerarquía de excepciones comunes
classDiagram
Exception <|-- SystemException
SystemException <|-- ArgumentException
SystemException <|-- NullReferenceException
SystemException <|-- InvalidOperationException
SystemException <|-- IOException
SystemException <|-- IndexOutOfRangeException
SystemException <|-- DivideByZeroException
SystemException <|-- FormatException
ArgumentException <|-- ArgumentNullException
ArgumentException <|-- ArgumentOutOfRangeException
IOException <|-- FileNotFoundException
IOException <|-- DirectoryNotFoundException
Exception <|-- ApplicationException
ApplicationException <|-- SaldoInsuficienteException
| Excepción | Cuándo ocurre |
|---|---|
ArgumentException | Argumento no válido |
ArgumentNullException | Argumento null cuando no debería |
ArgumentOutOfRangeException | Argumento fuera de rango |
NullReferenceException | Acceso a miembro de un objeto null |
InvalidOperationException | Operación no válida en el estado actual |
DivideByZeroException | División entre cero |
FormatException | Formato de cadena no válido al convertir |
IndexOutOfRangeException | Índice fuera de los límites del array |
FileNotFoundException | Archivo no encontrado |
IOException | Error general de entrada/salida |
OverflowException | Desbordamiento numérico |
StackOverflowException | Recursión infinita (no se puede capturar) |
7. Filtros de excepción: when
Puedes añadir condiciones extra a los bloques catch:
try
{
// Operación HTTP simulada
int statusCode = 404;
throw new HttpRequestException($"Error HTTP {statusCode}");
}
catch (HttpRequestException ex) when (ex.Message.Contains("404"))
{
Console.WriteLine("Recurso no encontrado (404)");
}
catch (HttpRequestException ex) when (ex.Message.Contains("500"))
{
Console.WriteLine("Error del servidor (500)");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Otro error HTTP: {ex.Message}");
}
8. Buenas prácticas
✅ Haz esto
// 1. Captura excepciones específicas
catch (FileNotFoundException ex) { ... }
// 2. Usa TryParse en lugar de Parse + catch
if (int.TryParse(input, out int numero))
{
// usar numero
}
// 3. Valida antes de operar (evita la excepción)
if (divisor != 0)
{
resultado = dividendo / divisor;
}
// 4. Usa 'using' para recursos (cierra automáticamente)
using StreamReader reader = new StreamReader("archivo.txt");
string contenido = reader.ReadToEnd();
// reader se cierra automáticamente al salir del ámbito
❌ No hagas esto
// 1. NO captures Exception genérica sin motivo
catch (Exception) { } // Se "traga" el error, oculta bugs
// 2. NO uses excepciones para flujo normal
try
{
int i = 0;
while (true) // ¡TERRIBLE!
Console.WriteLine(array[i++]);
}
catch (IndexOutOfRangeException) { }
// Usa: for (int i = 0; i < array.Length; i++)
// 3. NO lances Exception genérica
throw new Exception("algo salió mal"); // Poco informativo
// Mejor: throw new InvalidOperationException("No se puede...");
// 4. NO pierdas la traza de pila
catch (Exception ex)
{
throw ex; // ¡MAL! Pierde la traza de pila
throw; // BIEN: conserva la traza original
}
9. Ejercicios
Ejercicio 1: Calculadora robusta
Mejora la calculadora del módulo anterior: maneja todas las posibles excepciones (formato inválido, división por cero, overflow). Muestra mensajes claros al usuario y permite reintentar.
Ejercicio 2: Registro de usuarios
Crea una función RegistrarUsuario(string nombre, string email, int edad) que lance:
ArgumentNullExceptionsi nombre o email son null/vacíoArgumentOutOfRangeExceptionsi la edad no está entre 18 y 120FormatExceptionsi el email no contiene@
Captura cada excepción con mensajes apropiados.
Ejercicio 3: Lectura segura de archivo
Pide al usuario un nombre de archivo y muestra su contenido. Maneja: FileNotFoundException, IOException, UnauthorizedAccessException. Usa finally para confirmar que se liberaron los recursos.
Ejercicio 4: Banco con excepciones
Expande el ejemplo de CuentaBancaria:
- Añade transferencias entre cuentas
- Crea excepciones
CuentaNoEncontradaExceptionyLimiteTransferenciaException - Implementa un historial de transacciones
Resumen
| Concepto | Sintaxis / Ejemplo |
|---|---|
| try-catch | try { } catch (Exception ex) { } |
| finally | try { } catch { } finally { } |
| Lanzar excepción | throw new ArgumentException("msg"); |
| Re-lanzar | throw; (sin variable) |
| Excepción personalizada | class MiError : Exception { } |
| Filtro when | catch (Exception ex) when (condición) |
| TryParse (evitar excepción) | int.TryParse(s, out int n) |
| using (auto-dispose) | using var f = new StreamReader(...) |