Manejo de excepciones básico: try-catch
Los errores en programación son inevitables. Incluso el código más cuidadosamente escrito puede encontrarse con situaciones inesperadas: un archivo que no existe, una división por cero, o datos de entrada no válidos. En C#, estas situaciones problemáticas se conocen como excepciones, y el lenguaje nos proporciona herramientas específicas para manejarlas de manera controlada y elegante.
El manejo de excepciones es una técnica fundamental que permite que nuestros programas respondan apropiadamente ante errores, evitando que se cierren de manera inesperada y proporcionando información útil tanto para el usuario como para el desarrollador. En lugar de que el programa se detenga abruptamente, podemos capturar estos errores y tomar decisiones sobre cómo proceder.
En este artículo exploraremos los conceptos básicos del manejo de excepciones en C#, centrándonos en la estructura try-catch
, que nos permite detectar y gestionar errores de forma controlada. Aprenderemos qué son las excepciones, cómo se producen, y cómo podemos preparar nuestro código para manejarlas apropiadamente.
¿Qué son las excepciones?
Una excepción es una situación excepcional o inesperada que ocurre durante la ejecución de un programa y que interrumpe el flujo normal de las instrucciones. En términos técnicos, es un objeto que contiene información sobre un error que ha ocurrido.
Las excepciones pueden clasificarse en diferentes categorías:
Tipo de excepción | Descripción | Ejemplo |
---|---|---|
System.DivideByZeroException | División por cero | int resultado = 10 / 0; |
System.FormatException | Formato de datos incorrecto | int numero = int.Parse("abc"); |
System.IndexOutOfRangeException | Índice fuera del rango del array | array[10] en un array de 5 elementos |
System.NullReferenceException | Referencia a un objeto nulo | Usar un objeto que vale null |
System.ArgumentException | Argumento no válido | Pasar un parámetro incorrecto a un método |
Cuando ocurre una excepción y no se maneja adecuadamente, el programa se detiene y muestra un mensaje de error. Por ejemplo, ejecutar este código sin manejo de excepciones:
using System;
class Program
{
static void Main()
{
Console.WriteLine("Calculadora simple");
Console.Write("Ingresa el primer número: ");
int numero1 = int.Parse(Console.ReadLine());
Console.Write("Ingresa el segundo número: ");
int numero2 = int.Parse(Console.ReadLine());
int resultado = numero1 / numero2;
Console.WriteLine($"El resultado de {numero1} / {numero2} = {resultado}");
}
}
Si el usuario ingresa "abc" como primer número o "0" como segundo número, el programa se cerrará con un error.
La estructura try-catch
La estructura try-catch
es el mecanismo básico para manejar excepciones en C#. Su sintaxis es la siguiente:
try
{
// Código que puede generar una excepción
}
catch (TipoDeExcepcion nombreVariable)
{
// Código para manejar la excepción
}
Componentes de try-catch
Componente | Descripción |
---|---|
try | Bloque que contiene el código que puede generar una excepción |
catch | Bloque que se ejecuta si ocurre una excepción del tipo especificado |
TipoDeExcepcion | El tipo específico de excepción que queremos capturar |
nombreVariable | Variable que contiene información sobre la excepción capturada |
Ejemplo básico de manejo de excepciones
Vamos a mejorar nuestro ejemplo anterior añadiendo manejo de excepciones:
using System;
class Program
{
static void Main()
{
Console.WriteLine("Calculadora simple con manejo de errores");
try
{
Console.Write("Ingresa el primer número: ");
int numero1 = int.Parse(Console.ReadLine());
Console.Write("Ingresa el segundo número: ");
int numero2 = int.Parse(Console.ReadLine());
int resultado = numero1 / numero2;
Console.WriteLine($"El resultado de {numero1} / {numero2} = {resultado}");
}
catch (FormatException ex)
{
Console.WriteLine("Error: Has ingresado un valor que no es un número válido.");
Console.WriteLine($"Detalles del error: {ex.Message}");
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Error: No se puede dividir por cero.");
Console.WriteLine($"Detalles del error: {ex.Message}");
}
Console.WriteLine("El programa ha terminado correctamente.");
}
}
En este ejemplo mejorado:
- El código que puede fallar se coloca dentro del bloque
try
- Tenemos dos bloques
catch
específicos para diferentes tipos de excepciones - Si ocurre una
FormatException
(entrada no numérica), se ejecuta el primercatch
- Si ocurre una
DivideByZeroException
(división por cero), se ejecuta el segundocatch
- El programa continúa ejecutándose después del manejo de la excepción
Captura de excepciones genéricas
También podemos capturar cualquier tipo de excepción usando la clase base Exception
:
using System;
class Program
{
static void Main()
{
Console.WriteLine("Programa con manejo genérico de excepciones");
try
{
Console.Write("Ingresa un número: ");
string entrada = Console.ReadLine();
int numero = int.Parse(entrada);
int resultado = 100 / numero;
Console.WriteLine($"100 dividido por {numero} es {resultado}");
}
catch (Exception ex)
{
Console.WriteLine("Ha ocurrido un error inesperado:");
Console.WriteLine($"Tipo de error: {ex.GetType().Name}");
Console.WriteLine($"Mensaje: {ex.Message}");
}
}
}
Sin embargo, es recomendable capturar excepciones específicas cuando sea posible, ya que permite un manejo más preciso de cada tipo de error.
Múltiples bloques catch
Podemos tener múltiples bloques catch
para manejar diferentes tipos de excepciones de manera específica:
using System;
class Program
{
static void Main()
{
Console.WriteLine("Programa con múltiples manejadores de excepciones");
try
{
Console.Write("Ingresa el tamaño del array: ");
int tamaño = int.Parse(Console.ReadLine());
int[] numeros = new int[tamaño];
Console.Write("Ingresa un índice para acceder: ");
int indice = int.Parse(Console.ReadLine());
Console.Write("Ingresa un valor para almacenar: ");
int valor = int.Parse(Console.ReadLine());
numeros[indice] = valor;
Console.WriteLine($"Valor {valor} almacenado en la posición {indice}");
Console.WriteLine($"El valor almacenado es: {numeros[indice]}");
}
catch (FormatException)
{
Console.WriteLine("Error: Debes ingresar números enteros válidos.");
}
catch (ArgumentOutOfRangeException)
{
Console.WriteLine("Error: El tamaño del array debe ser un número positivo.");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Error: El índice está fuera del rango del array.");
}
catch (OutOfMemoryException)
{
Console.WriteLine("Error: No hay suficiente memoria para crear el array.");
}
catch (Exception ex)
{
Console.WriteLine($"Error inesperado: {ex.Message}");
}
}
}
Importante: Los bloques catch
se evalúan en orden. Por eso, las excepciones más específicas deben ir antes que las más generales.
Propiedades útiles de las excepciones
Las excepciones en C# proporcionan información valiosa a través de sus propiedades:
Propiedad | Descripción |
---|---|
Message | Descripción del error |
StackTrace | Rastro de la pila de llamadas donde ocurrió el error |
Source | Nombre de la aplicación o del objeto que causó el error |
InnerException | Excepción interna que causó esta excepción |
Ejemplo de uso de estas propiedades:
using System;
class Program
{
static void Main()
{
try
{
int resultado = ObtenerResultado();
Console.WriteLine($"Resultado: {resultado}");
}
catch (Exception ex)
{
Console.WriteLine("=== INFORMACIÓN DE LA EXCEPCIÓN ===");
Console.WriteLine($"Mensaje: {ex.Message}");
Console.WriteLine($"Tipo: {ex.GetType().Name}");
Console.WriteLine($"Origen: {ex.Source}");
Console.WriteLine("\nRastro de la pila:");
Console.WriteLine(ex.StackTrace);
}
}
static int ObtenerResultado()
{
// Este método causa intencionalmente una excepción
string texto = null;
return texto.Length; // Esto causará una NullReferenceException
}
}
Mejores prácticas básicas
Al trabajar con el manejo de excepciones, es importante seguir ciertas prácticas recomendadas:
1. Ser específico con las excepciones
// Bien: Captura específica
catch (FileNotFoundException)
{
Console.WriteLine("El archivo no se encontró.");
}
// Menos recomendado: Captura genérica
catch (Exception)
{
Console.WriteLine("Ocurrió un error.");
}
2. No ignorar las excepciones
// Mal: Ignorar la excepción
try
{
int numero = int.Parse(entrada);
}
catch (FormatException)
{
// No hacer nada es una mala práctica
}
// Bien: Al menos registrar o informar el error
try
{
int numero = int.Parse(entrada);
}
catch (FormatException)
{
Console.WriteLine("Formato de número no válido.");
return; // O tomar alguna acción apropiada
}
3. Proporcionar información útil al usuario
try
{
// Código que puede fallar
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("No tienes permisos para realizar esta operación.");
Console.WriteLine("Contacta con el administrador del sistema.");
}
Ejemplo práctico: Calculadora robusta
Vamos a crear una calculadora simple pero robusta que maneja múltiples tipos de excepciones:
using System;
class Program
{
static void Main()
{
Console.WriteLine("=== CALCULADORA ROBUSTA ===");
bool continuar = true;
while (continuar)
{
try
{
Console.WriteLine("\nSelecciona una operación:");
Console.WriteLine("1. Suma");
Console.WriteLine("2. Resta");
Console.WriteLine("3. Multiplicación");
Console.WriteLine("4. División");
Console.WriteLine("5. Salir");
Console.Write("Opción: ");
int opcion = int.Parse(Console.ReadLine());
if (opcion == 5)
{
continuar = false;
Console.WriteLine("¡Hasta luego!");
continue;
}
if (opcion < 1 || opcion > 4)
{
Console.WriteLine("Opción no válida. Elige del 1 al 5.");
continue;
}
Console.Write("Primer número: ");
double num1 = double.Parse(Console.ReadLine());
Console.Write("Segundo número: ");
double num2 = double.Parse(Console.ReadLine());
double resultado = 0;
string operacion = "";
switch (opcion)
{
case 1:
resultado = num1 + num2;
operacion = "+";
break;
case 2:
resultado = num1 - num2;
operacion = "-";
break;
case 3:
resultado = num1 * num2;
operacion = "*";
break;
case 4:
if (num2 == 0)
{
throw new DivideByZeroException("No se puede dividir por cero.");
}
resultado = num1 / num2;
operacion = "/";
break;
}
Console.WriteLine($"Resultado: {num1} {operacion} {num2} = {resultado}");
}
catch (FormatException)
{
Console.WriteLine("Error: Debes ingresar números válidos.");
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Error de división: {ex.Message}");
}
catch (OverflowException)
{
Console.WriteLine("Error: El número es demasiado grande o pequeño.");
}
catch (Exception ex)
{
Console.WriteLine($"Error inesperado: {ex.Message}");
Console.WriteLine("Por favor, inténtalo de nuevo.");
}
}
}
}
Esta calculadora maneja múltiples tipos de errores y permite al usuario continuar usando el programa incluso después de un error.
Resumen
El manejo de excepciones con try-catch
es una herramienta fundamental en C# que nos permite crear programas más robustos y resistentes a errores. Hemos aprendido que las excepciones son situaciones inesperadas que pueden interrumpir la ejecución normal del programa, y que podemos capturarlas y manejarlas de manera controlada usando bloques try-catch
.
Los conceptos clave incluyen la importancia de ser específico al capturar excepciones, proporcionar información útil al usuario cuando ocurre un error, y nunca ignorar las excepciones completamente. Con estas técnicas básicas, podemos transformar programas frágiles que se cierran ante cualquier error en aplicaciones robustas que pueden recuperarse y continuar funcionando incluso cuando las cosas no salen según lo planeado.
En los próximos artículos exploraremos estructuras de datos más avanzadas, pero el manejo de excepciones que hemos aprendido aquí será una herramienta valiosa que utilizaremos constantemente en nuestro desarrollo con C#.