try, catch y finally
En resumen: Con varios bloques catch atrapas tipos distintos de excepciones, de lo concreto a lo general, y finally se ejecuta siempre, con error o sin él.
Problema
La cuenta para cero personas
El programa divide la cuenta del restaurante a partes iguales. Ya conoces la entrada incorrecta, y la atrapas:
class Program{ static void Main() { try { int bill = int.Parse(Console.ReadLine()); int people = int.Parse(Console.ReadLine()); Console.WriteLine("Por persona: " + bill / people + " €"); } catch (FormatException) { Console.WriteLine("Escribe números enteros."); } }}La cuenta es de 90 €, y alguien escribe 0 como número de personas. El programa vuelve a detenerse:
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. at Program.Main() in /projects/Bill/Program.cs:line 9catch está ahí, pero no ayuda. Solo atrapa FormatException, y esta es otra clase de excepción.
La idea
Varios bloques catch
Después de un try pueden ir varios bloques catch, uno para cada tipo de error:
try{ int bill = int.Parse(Console.ReadLine()); int people = int.Parse(Console.ReadLine()); Console.WriteLine("Por persona: " + bill / people + " €");}catch (FormatException){ Console.WriteLine("Escribe números enteros.");}catch (DivideByZeroException){ Console.WriteLine("Las personas no pueden ser 0.");}| Entrada | Salida |
|---|---|
| 90 y 3 | Por persona: 30 € |
| 90 y 0 | Las personas no pueden ser 0. |
| 90 y tres | Escribe números enteros. |
Los bloques se comprueban de arriba abajo. Se ejecuta el primero cuyo tipo coincide, y solo ese. Los demás se saltan.
try{ int bill = int.Parse(Console.ReadLine()); int people = int.Parse(Console.ReadLine()); Console.WriteLine("Por persona: " + bill / people + " €");}catch (FormatException){ Console.WriteLine("Escribe números enteros.");}catch (DivideByZeroException){ Console.WriteLine("Las personas no pueden ser 0.");}Paso a paso, en palabras
- Primera ejecución: cuenta de 90 €, las personas son 0.
- 90 / 0 es una división entre cero: se lanza DivideByZeroException.
- El primer catch es para FormatException. No coincide, se salta.
- El segundo coincide. Se ejecuta él y solo él.
- Segunda ejecución: en lugar del número de personas se ha escrito «tres».
- int.Parse no puede sacar un número: se lanza FormatException.
- Ya coincide el primer catch. Al segundo no se llega.
- Los bloques se comprueban de arriba abajo. Se ejecuta el primero que coincide.
Más precisamente, para curiosos
catchtambién puede tener una condición:catch (FormatException) when (text == ""). El bloque solo atrapa si la condición es verdadera.
La idea
La familia de las excepciones
Las excepciones son clases, relacionadas por herencia. Aquí están las más comunes:
Exception├── SystemException│ ├── ArithmeticException│ │ ├── DivideByZeroException│ │ └── OverflowException│ ├── FormatException│ ├── IndexOutOfRangeException│ ├── NullReferenceException│ ├── ArgumentException│ │ ├── ArgumentNullException│ │ └── ArgumentOutOfRangeException│ └── IOException archivos, red└── ApplicationExceptioncatch atrapa el tipo que nombras y todas sus clases hijas. Es el polimorfismo del módulo sobre la herencia: una variable de un tipo padre puede apuntar a un objeto de una clase hija. Por eso catch (Exception e) lo atrapa todo:
static void Split(string billText, string peopleText){ try { int bill = int.Parse(billText); int people = int.Parse(peopleText); Console.WriteLine("Por persona: " + bill / people + " €"); } catch (Exception e) { Console.WriteLine(e.GetType().Name + ": " + e.Message); }}
Split("90", "3");Split("90", "0");Split("90", "tres");Por persona: 30 €DivideByZeroException: Attempted to divide by zero.FormatException: The input string 'tres' was not in a correct format.e.GetType().Name da el nombre de la clase real del objeto. La variable es de tipo Exception, pero el objeto es de una clase hija.
Más precisamente, para curiosos
StackOverflowExceptionno se puede atrapar de ninguna manera: .NET detiene el programa enseguida.OutOfMemoryExceptionsí, pero rara vez tiene sentido.ApplicationExceptioniba a ser la clase padre de las excepciones que definen los programas. En la práctica, tus propias excepciones las haces directamente a partir deException.
- Atrapa de la forma más concreta que puedas. catch (Exception e) también esconde errores que no esperabas, como un fallo en tu propio código.
Pruébalo
¿Qué catch?
¿Qué imprimirá el programa?
int[] seats = { 4, 2, 6 };try{ Console.WriteLine("Mesa 4: " + seats[3] + " sitios");}catch (ArithmeticException){ Console.WriteLine("División entre cero");}catch (SystemException){ Console.WriteLine("Error de ejecución");}catch (Exception){ Console.WriteLine("Error general");}seats[3] está fuera del array de 3 elementos. IndexOutOfRangeException no es una ArithmeticException, así que el primer bloque se salta. Es una clase hija de SystemException, así que el segundo coincide y se ejecuta. Al tercero nunca se llega, aunque también coincidiría.Pruébalo
Encuentra el error
Elige la línea con el error.
Exception es la clase padre de DivideByZeroException. El primer catch lo atrapa todo, y al segundo nunca se puede llegar. El compilador lo dice: "CS0160: A previous catch clause already catches all exceptions of this or of a super type ('Exception')". La regla es de lo concreto a lo general: intercambia los dos bloques.La idea
El compilador no te obliga
Java tiene excepciones comprobadas: el compilador no acepta el programa hasta que las atrapas o las declaras. C# no tiene ninguna así. Ninguna excepción tiene que atraparse:
static void PrintReceipt(){ Console.WriteLine("Imprimo el recibo..."); Thread.Sleep(300); Console.WriteLine("Listo.");}
PrintReceipt();Imprimo el recibo...Listo.Thread.Sleep pausa el programa 300 milisegundos y puede lanzar una excepción, pero el compilador no pide nada. Lo mismo pasa con int.Parse.
Así que la responsabilidad es tuya. Qué excepciones lanza un método está escrito en su documentación, en la sección «Exceptions». Léela antes de decidir si atrapar.
La idea
finally: siempre
La impresora de la caja tiene que liberarse después de cada recibo, o el siguiente no se imprimirá. Pase lo que pase, salga bien o haya un error:
static void PrintReceipt(string qtyText){ Console.WriteLine("La impresora está ocupada."); try { int qty = int.Parse(qtyText); Console.WriteLine("Café: " + qty + " x 2 € = " + qty * 2 + " €"); } catch (FormatException) { Console.WriteLine("Cantidad incorrecta: " + qtyText); } finally { Console.WriteLine("La impresora está libre."); }}
PrintReceipt("3");PrintReceipt("tres");La impresora está ocupada.Café: 3 x 2 € = 6 €La impresora está libre.La impresora está ocupada.Cantidad incorrecta: tresLa impresora está libre.finally se ejecuta siempre: cuando todo sale bien, cuando la excepción se atrapa, e incluso cuando ninguno de estos bloques catch la atrapa. En este último caso finally se ejecuta, y la excepción sigue subiendo hasta el siguiente try que pueda atraparla.
¿Por qué no una simple línea después del try/catch? Porque esa línea se salta si la excepción es de un tipo que catch no atrapa, o si try tiene un return. finally no se salta.
Más precisamente, para curiosos
- También puedes tener
tryconfinallyy sincatch: la excepción no se atrapa, pero la limpieza se ejecuta.
- Para los recursos que hay que cerrar, como archivos y conexiones a bases de datos, hay una forma más corta:
using. Te encontrarás con ella cuando trabajes con archivos y bases de datos. Cierra el recurso por sí sola, igual que lo haríafinally.
finallyno se ejecuta solo si se detiene todo el programa, por ejemplo conEnvironment.Exit(0).
- Si la excepción no se atrapa en ningún sitio del programa, .NET puede detenerlo sin ejecutar los bloques
finally. Así que atrapa las excepciones al menos enMainsi hay algo importante enfinally.
Pruébalo
finally y return
¿Qué imprimirá el programa?
static string Reserve(int guests){ try { if (guests > 6) { return "No hay mesa para " + guests; } return "Mesa para " + guests; } finally { Console.WriteLine("Registré el intento."); }}
Console.WriteLine(Reserve(8));Un return en try no se salta finally. El método ejecuta primero finally, y después devuelve el texto. Por eso «Registré el intento.» se imprime antes del resultado, que WriteLine recibe solo cuando el método ha terminado.
Ejercicio
Programas con varios bloques catch
Resuélvelas en tu editor. No imprimas mensajes para pedir datos.
1. Precio por persona. Los precios están en el array { 120, 75, 300 }. Lee un número de producto (desde 1) y el número de personas, e imprime el precio por persona con división entera. Atrapa tres cosas: un producto que no existe, cero personas, y todo lo demás (entonces imprime el mensaje de la excepción).
| Entrada | Salida |
|---|---|
| 2 y 3 | Por persona: 25 € |
| 5 y 2 | No existe ese producto. |
| 1 y 0 | El número de personas no puede ser 0. |
| 1 y dos | Error: The input string 'dos' was not in a correct format. |
2. Calculadora. Lee un número entero, un operador (+, -, * o /) y un segundo número entero, cada uno en su propia línea. Imprime la cuenta. Con texto en lugar de un número di «Escribe números enteros.», con división entre cero «División entre cero.», y con cualquier otro operador «Operador desconocido: » seguido del operador.
84/484 / 4 = 2184/0División entre cero.Más tareas
- El datáfono. El método
Pay(string amountText)imprime «El datáfono está conectado.», después «Pagado: X €» o un mensaje sobre la entrada incorrecta, y enfinally«El datáfono está apagado.». Llámalo con «25» y con «veinticinco». - Nota media sin caídas. Lee 5 líneas con notas. Sáltate las que no son números enteros (una excepción) o no están entre 2 y 6 (un
if). Imprime la media con división entera. Si ninguna nota es válida, la división entre cero lanza: atrápala e imprime «No hay notas válidas.» - Predice y comprueba. Escribe un
tryconcatchyfinally, donde cada bloque imprime una línea. Predice la salida cuando sale bien, con una excepción atrapada y con una no atrapada, y después ejecútalo.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Métodos de instancia · Herencia · Polimorfismo · Excepciones
- Continúa en: Lanzar excepciones y crear las tuyas · Códigos hash y funciones hash · Hilos · Programación en red
- Ejercicios sobre el tema: Errores y excepciones