Ir al contenido
wannacode

Excepciones

En resumen: Una excepción es un objeto que describe un error ocurrido mientras el programa se ejecuta: el stack trace muestra dónde, y try y catch lo atrapan para que el programa pueda seguir.

Problema

12a en lugar de 12

La taquilla del concierto pregunta cuántas entradas quieres. Una entrada cuesta 15 €.

Program.cs
class Program
{
static void Main()
{
Console.Write("¿Cuántas entradas? ");
int tickets = int.Parse(Console.ReadLine());
Console.WriteLine("Total: " + tickets * 15 + " €");
}
}

Con la entrada 12 el programa dice «Total: 180 €». Pero el cliente tiene prisa y pulsa una letra de más:

La consola
¿Cuántas entradas? 12a
Unhandled exception. System.FormatException: The input string '12a' was not in a correct format.
at System.Number.ThrowFormatException[TChar](ReadOnlySpan`1 value)
at System.Int32.Parse(String s)
at Program.Main() in /projects/Tickets/Program.cs:line 6

El programa se detiene. En lugar del total sale un texto largo, y la línea «Total» nunca se alcanza. ¿Qué significa este texto, y cómo evitas que el programa se detenga por una letra?

La idea

Excepción

int.Parse tiene que devolver un número. No puede sacar un número de «12a», y devolver un número cualquiera sería mentir. Así que el método crea un objeto que describe el problema y lo lanza. Este objeto se llama excepción.

Si nadie la atrapa, el programa se detiene y muestra qué pasó. Este texto se llama stack trace, y se lee de arriba abajo:

  • La primera línea: el tipo de excepción y el mensaje. FormatException: The input string '12a' was not in a correct format.: el número tiene un formato incorrecto, y aquí está el texto.
  • Las líneas con «at»: dónde ocurrió. Es la pila de llamadas de la lección de recursión, de dentro hacia fuera. Arriba está el método que lanzó la excepción, y abajo está Main.
  • Tu línea: la primera línea «at» que apunta a tu archivo. Aquí: Program.cs:line 6. Las líneas de encima están en el código de .NET, y rara vez las necesitas.

Los mensajes están en inglés porque los escribe el lenguaje, no tú.

Pruébalo

Encuentra el error

El programa debería imprimir la última nota del examen. Se detiene con esto:

Error
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
at Program.LastScore(Int32[] scores) in /projects/Tickets/Program.cs:line 5
at Program.Main() in /projects/Tickets/Program.cs:line 11

Lee el stack trace. ¿En qué línea del programa está el error?

Elige la línea con el error.

La idea

try y catch

Una excepción se puede atrapar. El código que podría lanzar va en try. Lo que debe pasar cuando hay un error, lo escribes en catch:

Console.Write("¿Cuántas entradas? ");
try
{
int tickets = int.Parse(Console.ReadLine());
Console.WriteLine("Total: " + tickets * 15 + " €");
}
catch (FormatException)
{
Console.WriteLine("Eso no es un número entero.");
}
Console.WriteLine("¡Gracias!");
Entrada
12a
Salida
¿Cuántas entradas? Eso no es un número entero.
¡Gracias!
  • Entre los paréntesis de catch escribes qué tipo de excepción atrapas.
  • Si todo en try sale bien, catch se salta. Con la entrada 12 la salida es «Total: 180 €» y «¡Gracias!».
  • Después de catch el programa sigue adelante. Ya no se detiene.

Console.Write("¿Cuántas entradas? ");
try
{
int tickets = int.Parse(Console.ReadLine());
Console.WriteLine("Total: " + tickets * 15 + " €");
}
catch (FormatException)
{
Console.WriteLine("Eso no es un número entero.");
}
Console.WriteLine("¡Gracias!");
Paso a paso, en palabras
  1. El programa pregunta cuántas entradas. Escribes «12a».
  2. En try: int.Parse intenta sacar un número de «12a».
  3. No puede. El método lanza FormatException.
  4. La ejecución salta directamente a catch. La línea con «Total» se salta.
  5. catch es justo para este tipo de error: imprime el mensaje para el cliente.
  6. Después de catch el programa sigue. No se detiene.
Más precisamente, para curiosos
  • Una variable declarada en try (aquí tickets) existe solo hasta la llave de cierre de try. Si también la necesitas después del bloque, declárala antes de try.
  • En C# puedes dejar la excepción sin nombre: catch (FormatException). Lo haces cuando no la necesitas. Si escribes un nombre y no lo usas, el compilador da un aviso.

Pruébalo

¿Qué líneas se ejecutan?

¿Qué imprimirá el programa?

Console.WriteLine("Inicio");
try
{
Console.WriteLine("Leo");
int seats = int.Parse("diez");
Console.WriteLine("Leí " + seats);
}
catch (FormatException)
{
Console.WriteLine("Entrada incorrecta");
}
Console.WriteLine("Fin");
Respuesta

La idea

Qué dice la excepción

La excepción es un objeto, y e en catch (FormatException e) es la variable que apunta a él. El objeto tiene una manera de darte el mensaje: e.Message.

try
{
int tickets = int.Parse(Console.ReadLine());
Console.WriteLine("Total: " + tickets * 15 + " €");
}
catch (FormatException e)
{
Console.WriteLine("Error: " + e.Message);
}
Entrada
12a
Salida
Error: The input string '12a' was not in a correct format.

El mensaje te sirve a ti cuando buscas el error. Al cliente normalmente le muestras tu propio texto, en su idioma, como «Eso no es un número entero.»

La idea

Pregunta hasta que esté bien

El cliente se equivocó. No tienes que detenerte. Pregunta otra vez:

int tickets;
while (true)
{
Console.Write("¿Cuántas entradas? ");
try
{
tickets = int.Parse(Console.ReadLine());
break;
}
catch (FormatException)
{
Console.WriteLine("Escribe un número entero, por ejemplo 3.");
}
}
Console.WriteLine("Total: " + tickets * 15 + " €");
Entrada
12a
doce
12
Salida
¿Cuántas entradas? Escribe un número entero, por ejemplo 3.
¿Cuántas entradas? Escribe un número entero, por ejemplo 3.
¿Cuántas entradas? Total: 180 €

while (true) sigue dando vueltas hasta que break lo detiene. Y a break solo se llega si Parse sale bien. Con un error, la ejecución salta a catch, y después el bucle vuelve a empezar y pregunta otra vez.

Más precisamente, para curiosos
  • Cuando una entrada incorrecta es algo normal, C# tiene una manera más práctica: int.TryParse. No lanza ninguna excepción. Devuelve true o false y escribe el número en el parámetro out de la lección sobre parámetros:
int tickets;
Console.Write("¿Cuántas entradas? ");
while (!int.TryParse(Console.ReadLine(), out tickets))
{
Console.WriteLine("Escribe un número entero, por ejemplo 3.");
Console.Write("¿Cuántas entradas? ");
}
Console.WriteLine("Total: " + tickets * 15 + " €");

Las excepciones son para situaciones que no esperas a menudo. Para comprobar la entrada, TryParse es más corto y más rápido.

Pruébalo

División entre cero

No toda excepción viene de la entrada. El valor medio de un pedido es la facturación dividida entre el número de pedidos. Pero el primer día no hay pedidos. Con números enteros, revenue / orders detiene el programa:

Error
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.

¿Y con números decimales?

¿Qué imprimirá el programa?

int revenue = 90;
int orders = 0;
double average = (double)revenue / orders;
Console.WriteLine(average);
Respuesta
Más precisamente, para curiosos
  • Aquí la salida es Infinity. En una computadora con configuración regional, por ejemplo española o estadounidense, C# imprime el signo ∞.
  • Los números enteros no tienen ningún valor para el infinito, así que lo único que queda es la excepción. Para los números decimales, las reglas vienen del estándar IEEE 754 de números de coma flotante, que siguen todos los lenguajes.

Ejercicio

Programas que no se detienen

Resuélvelas en tu editor. No imprimas mensajes para pedir datos, para que tu salida coincida con el ejemplo.

1. Texto a número. Escribe un método ToNumber(string text) que devuelva el texto como double. Lee dos líneas. Para cada una, imprime el número o un mensaje de que no es un número. Atrapa la excepción en Main.

Entrada
123.45
123.abc
Salida
El número es: 123.45
Error: 123.abc no es un número.

En una computadora con configuración regional española, double.Parse quiere una coma: 123,45. Mira la lección «Entrada y salida».

2. No hay tal producto. Los precios están en el array { 120, 75, 300, 45, 89 }. Lee un número de producto del 1 al 5 e imprime su precio. Si el número está fuera del array, atrapa la excepción y dilo. Con la entrada 2 la salida es Precio: 75 €.

Entrada
7
Salida
No hay ningún producto con el número 7.

3. Pedido medio. Lee la facturación y el número de pedidos (números enteros) e imprime la media con división entera. Si no hay pedidos, atrapa la excepción. Con la entrada 900 y 4 la salida es Media: 225 €.

Entrada
90
0
Salida
Todavía no hay pedidos.

Más tareas

  • Edad, hasta que esté bien. Pregunta por una edad hasta que la entrada sea un número entero del 0 al 120. Para un texto como «veinte» di «Eso no es un número.», y para 150 di «La edad tiene que ser del 0 al 120.» Lo uno es una excepción, lo otro es un if.
  • Suma de los válidos. Lee 5 líneas. Suma las que son números enteros y cuenta las que se saltan: con la entrada 10, abc, 5, 7.5, 3 la salida es Suma: 18, saltadas: 2.
  • División a petición. Lee dos números enteros e imprime el cociente. Con texto en lugar de un número y con división entre cero, el programa no se detiene. Vuelve a pedir los dos números.
  • Sin excepción. Resuelve la tarea 1 con double.TryParse en lugar de try/catch.

Test

Ponte a prueba

1. ¿Qué es una excepción?
2. El programa se detuvo con un largo texto de error. ¿Por dónde empiezas a leer?
3. Un try tiene cuatro líneas. La segunda lanza una excepción, y catch la atrapa. ¿Qué pasa con la tercera y la cuarta?
4. ¿Qué pasa con int x = 90 / orders; cuando orders = 0?
5. ¿Atrapa catch (FormatException e) el error de scores[10] en un array de 5 elementos?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·