Ir al contenido principal

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 primer catch
  • Si ocurre una DivideByZeroException (división por cero), se ejecuta el segundo catch
  • 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#.