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