Lección 9 · ~12 min · ← Lección 8
Excepciones: cuando la ejecución va mal
Los errores de compilación te paran antes de lanzar; las excepciones son los problemas que aparecen mientras corre — entrada mala, claves ausentes, división por cero. La respuesta de C#: try la parte arriesgada, catch el fallo, finally la limpieza.
try / catch: contener la explosión
string[] entradas = { "42", "abc" };
foreach (string s in entradas)
{
try
{
Console.WriteLine("parseado: " + int.Parse(s));
}
catch (FormatException) // captura por TIPO — solo este fallo
{
Console.WriteLine("no es un número: " + s);
}
}
Sin el try/catch, el segundo int.Parse mataría todo el programa. Con él, el fallo queda contenido en una iteración y el bucle continúa. Captura el tipo más específico que puedas manejar — catch (Exception) captura todo y normalmente esconde bugs.
El objeto excepción: qué falló, por escrito
int Dividir(int a, int b)
{
if (b == 0)
{
throw new Exception("la división por cero no está definida"); // lánzala tú
}
return a / b;
}
try
{
Console.WriteLine(Dividir(10, 2));
Console.WriteLine(Dividir(1, 0));
}
catch (Exception e) // e ES la excepción — lee su Message
{
Console.WriteLine("capturado: " + e.Message);
}
finally
{
Console.WriteLine("siempre corre"); // éxito O fallo — la limpieza vive aquí
}
finally corre pase lo que pase: éxito, fallo capturado o excepción sin capturar. Es donde se cierran archivos y conexiones — el motor de aquí no tiene archivos, pero la forma es universal.
Los tipos forman una familia — captura la rama que quieres
Exception // la raíz — todo es una de estas
└── SystemException
├── FormatException // int.Parse("abc")
├── NullReferenceException // usaste un objeto null
├── IndexOutOfRangeException // índice de array fuera de rango
├── DivideByZeroException // división entera por cero
└── ArgumentException // pasaste algo inválido
└── ArgumentNullException // ...específicamente: null
Capturar un padre captura a todos sus hijos: catch (Exception e) captura todo; catch (SystemException e) la familia del sistema. Ordena varios catch del más específico al más general — el primero que coincide gana.
No uses try/catch para lo que puedes evitar
La división idiomática: excepciones para lo excepcional, TryParse para lo esperado:
string[] entradas = { "42", "xyz" };
foreach (string s in entradas)
{
if (int.TryParse(s, out int n)) // el fallo es un resultado normal aquí
{
Console.WriteLine("obtuve " + n);
}
else
{
Console.WriteLine("ignoré " + s);
}
}
Regla práctica: si "falló" es una rama normal de tu lógica (¡entrada de usuario!), usa el método Try. Si el fallo significa que algo está realmente roto, deja volar la excepción y captúrala en el nivel que sí puede hacer algo al respecto.
Lanzar las propias
Cuando TU método recibe un argumento con el que no puede trabajar, la convención de C# es lanzar ArgumentException (o un hijo más específico) con un mensaje que diga qué está mal:
void EstablecerEdad(int edad)
{
if (edad < 0)
{
throw new ArgumentException("la edad no puede ser negativa: " + edad);
}
// ...guárdala
}
Lanza pronto, con un mensaje claro — la alternativa (guardar tonterías en silencio) se convierte en la sesión de depuración de otra persona a las 2 de la mañana.
Practica ahora
- Envuelve
int.Parse(Console.ReadLine())en try/catch; repite hasta que el usuario (o la caja de entrada) dé un número válido. - Lanza
ArgumentExceptiondesde un métodoEstablecerPuntuacion(int)para entrada negativa; captúrala e imprime el mensaje. - Luego lee los fallos cotidianos del descifrador de errores — primos en tiempo de compilación de todo lo de aquí.