CSharp

Tutorial CSharp: Excepciones

Aprende a manejar excepciones en C# con try-catch, múltiples catch, finally y throw para crear programas robustos y controlados.

Aprende CSharp y certifícate

Try-catch básico

El manejo de excepciones es una parte fundamental de la programación en C# que nos permite controlar los errores que pueden ocurrir durante la ejecución de nuestro programa. En lugar de permitir que un error detenga completamente la aplicación, podemos capturar estos errores y responder a ellos de manera controlada.

La estructura básica para manejar excepciones en C# es el bloque try-catch. Este mecanismo nos permite "intentar" ejecutar un bloque de código y "capturar" cualquier excepción que pueda ocurrir durante su ejecución.

Sintaxis básica

La estructura try-catch tiene la siguiente forma:

try
{
    // Código que podría generar una excepción
}
catch (Exception ex)
{
    // Código que se ejecuta si ocurre una excepción
}

Veamos un ejemplo sencillo para entender cómo funciona:

using System;

class Program
{
    static void Main()
    {
        try
        {
            Console.Write("Introduce un número: ");
            string input = Console.ReadLine();
            int numero = int.Parse(input);
            Console.WriteLine($"Has introducido el número: {numero}");
        }
        catch (Exception ex)
        {
            Console.WriteLine("Ha ocurrido un error: " + ex.Message);
        }
        
        Console.WriteLine("El programa continúa ejecutándose.");
    }
}

En este ejemplo, si el usuario introduce algo que no sea un número (como una letra), el método int.Parse() generará una excepción. En lugar de que el programa se detenga con un error, el bloque catch capturará la excepción y mostrará un mensaje amigable.

¿Qué es una excepción?

Una excepción es un objeto que contiene información sobre un error que ha ocurrido durante la ejecución del programa. Cuando se produce un error, C# crea un objeto de excepción y lo "lanza" (throw). Si este objeto no es capturado por un bloque catch, el programa terminará abruptamente.

Todas las excepciones en C# derivan de la clase base System.Exception, que proporciona propiedades útiles como:

  • Message: Descripción del error
  • StackTrace: Secuencia de llamadas que llevaron al error
  • InnerException: Excepción que causó la excepción actual (si existe)

Ejemplo práctico

Veamos un ejemplo más completo que muestra cómo el try-catch puede ayudarnos a manejar diferentes situaciones de error:

using System;

class Program
{
    static void Main()
    {
        int[] numeros = { 10, 20, 30 };
        
        try
        {
            // Intentamos acceder a una posición que no existe en el array
            Console.WriteLine($"El cuarto elemento es: {numeros[3]}");
            
            // Este código nunca se ejecutará si ocurre la excepción anterior
            Console.WriteLine("Esta línea no se ejecutará si hay error");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Se produjo un error: {ex.Message}");
            Console.WriteLine($"Tipo de excepción: {ex.GetType().Name}");
        }
        
        Console.WriteLine("El programa continúa después del error");
    }
}

En este caso, intentamos acceder al cuarto elemento de un array que solo tiene tres elementos. Esto generará una excepción de tipo IndexOutOfRangeException. El bloque catch capturará esta excepción y mostrará información sobre ella.

Flujo de ejecución

Es importante entender el flujo de ejecución cuando se trabaja con bloques try-catch:

  1. El código dentro del bloque try se ejecuta normalmente.
  2. Si no ocurre ninguna excepción, el bloque catch se ignora.
  3. Si ocurre una excepción en el bloque try, la ejecución de este bloque se detiene inmediatamente.
  4. El control se transfiere al bloque catch correspondiente.
  5. Después de ejecutar el bloque catch, el programa continúa con la siguiente instrucción después del bloque try-catch.

Veamos esto con un ejemplo:

using System;

class Program
{
    static void Main()
    {
        Console.WriteLine("Inicio del programa");
        
        try
        {
            Console.WriteLine("Inicio del bloque try");
            int resultado = 10 / 0; // Esto generará una excepción
            Console.WriteLine("Esta línea nunca se ejecutará");
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("Se capturó una excepción: " + ex.Message);
        }
        
        Console.WriteLine("Fin del programa");
    }
}

La salida de este programa será:

Inicio del programa
Inicio del bloque try
Se capturó una excepción: Attempted to divide by zero.
Fin del programa

Observa que la línea "Esta línea nunca se ejecutará" no aparece en la salida porque la ejecución del bloque try se interrumpe cuando ocurre la excepción.

Cuándo usar try-catch

Es importante usar los bloques try-catch de manera adecuada. Algunas situaciones donde son especialmente útiles:

  • Operaciones de entrada/salida: Lectura/escritura de archivos, conexiones de red.
  • Conversión de tipos: Cuando se convierte un string a un tipo numérico.
  • Acceso a elementos de colecciones: Cuando se accede a índices que podrían no existir.
  • Operaciones matemáticas: División por cero, desbordamientos.

Sin embargo, no es recomendable usar try-catch para controlar el flujo normal del programa. Por ejemplo:

// Mal uso de try-catch (no recomendado)
try
{
    if (usuario.EsAdmin)
    {
        // Código para administradores
    }
    else
    {
        throw new Exception("No eres administrador");
    }
}
catch (Exception)
{
    // Código para usuarios normales
}

En este caso, sería mejor usar una simple estructura if-else sin excepciones.

Ejemplo de aplicación real

Veamos un ejemplo más práctico donde el manejo de excepciones es útil:

using System;

class Program
{
    static void Main()
    {
        bool entradaValida = false;
        int edad = 0;
        
        while (!entradaValida)
        {
            try
            {
                Console.Write("Por favor, introduce tu edad: ");
                string entrada = Console.ReadLine();
                edad = int.Parse(entrada);
                
                if (edad < 0 || edad > 120)
                {
                    Console.WriteLine("La edad debe estar entre 0 y 120 años.");
                    continue;
                }
                
                entradaValida = true;
            }
            catch (FormatException)
            {
                Console.WriteLine("Error: Debes introducir un número entero.");
            }
            catch (OverflowException)
            {
                Console.WriteLine("Error: El número es demasiado grande o pequeño.");
            }
        }
        
        Console.WriteLine($"Tu edad es: {edad} años");
    }
}

En este ejemplo, usamos un bucle para solicitar repetidamente la edad del usuario hasta que se introduzca un valor válido. El bloque try-catch nos permite manejar diferentes tipos de errores que pueden ocurrir durante la conversión.

El manejo de excepciones con try-catch es una herramienta fundamental para crear aplicaciones robustas en C#. Te permite anticipar y responder a situaciones de error de manera elegante, mejorando la experiencia del usuario y evitando que tu programa se detenga inesperadamente.

Múltiples catch y finally

En situaciones reales de programación, los errores pueden ser de diversos tipos y requerir diferentes respuestas. C# nos permite manejar distintos tipos de excepciones de manera específica mediante el uso de múltiples bloques catch. Además, el bloque finally nos proporciona un mecanismo para ejecutar código independientemente de si ocurre una excepción o no.

Múltiples bloques catch

Cuando trabajamos con código que puede generar diferentes tipos de excepciones, es útil tratarlas de manera específica:

try
{
    // Código que podría generar diferentes tipos de excepciones
}
catch (FormatException ex)
{
    // Se ejecuta si ocurre un error de formato
}
catch (DivideByZeroException ex)
{
    // Se ejecuta si ocurre una división por cero
}
catch (Exception ex)
{
    // Se ejecuta para cualquier otro tipo de excepción
}

El orden de los bloques catch es crucial. C# evalúa los bloques catch en el orden en que aparecen, y ejecuta el primero que coincida con el tipo de excepción lanzada. Por esta razón, siempre debemos colocar las excepciones más específicas primero y las más generales después.

Veamos un ejemplo práctico:

using System;

class Program
{
    static void Main()
    {
        Console.WriteLine("Calculadora simple");
        
        try
        {
            Console.Write("Introduce el primer número: ");
            int num1 = int.Parse(Console.ReadLine());
            
            Console.Write("Introduce el segundo número: ");
            int num2 = int.Parse(Console.ReadLine());
            
            int resultado = num1 / num2;
            Console.WriteLine($"El resultado de {num1} / {num2} es: {resultado}");
        }
        catch (FormatException)
        {
            Console.WriteLine("Error: Debes introducir números enteros válidos.");
        }
        catch (DivideByZeroException)
        {
            Console.WriteLine("Error: No es posible dividir por cero.");
        }
        catch (OverflowException)
        {
            Console.WriteLine("Error: El número es demasiado grande o pequeño.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error inesperado: {ex.Message}");
        }
    }
}

En este ejemplo:

  • Si el usuario introduce algo que no es un número, se lanzará una FormatException
  • Si el segundo número es cero, se lanzará una DivideByZeroException
  • Si el número es demasiado grande para un int, se lanzará una OverflowException
  • Cualquier otra excepción será capturada por el último catch

Captura de excepciones con filtros

A partir de C# 6.0, podemos usar filtros de excepciones con la palabra clave when para añadir condiciones adicionales a nuestros bloques catch:

try
{
    // Código que podría generar excepciones
}
catch (Exception ex) when (ex.Message.Contains("específico"))
{
    // Solo se ejecuta si el mensaje de la excepción contiene "específico"
}

Esto nos permite un control más granular sobre qué excepciones manejar:

using System;
using System.IO;

class Program
{
    static void Main()
    {
        try
        {
            string contenido = File.ReadAllText("datos.txt");
            Console.WriteLine(contenido);
        }
        catch (FileNotFoundException ex) when (ex.FileName.Contains("datos"))
        {
            Console.WriteLine("El archivo de datos no se encuentra.");
        }
        catch (IOException ex) when (ex.Message.Contains("being used"))
        {
            Console.WriteLine("El archivo está siendo utilizado por otro proceso.");
        }
        catch (IOException)
        {
            Console.WriteLine("Error general de entrada/salida.");
        }
    }
}

El bloque finally

El bloque finally contiene código que se ejecutará siempre, independientemente de si ocurre una excepción o no. Es especialmente útil para liberar recursos o realizar tareas de limpieza:

try
{
    // Código que podría generar excepciones
}
catch (Exception ex)
{
    // Manejo de la excepción
}
finally
{
    // Este código siempre se ejecuta
}

El bloque finally se ejecuta en los siguientes casos:

  • Después del bloque try si no ocurre ninguna excepción
  • Después del bloque catch si ocurre una excepción
  • Incluso si hay una instrucción return dentro del try o catch

Veamos un ejemplo práctico con un escenario común: trabajar con archivos.

using System;
using System.IO;

class Program
{
    static void Main()
    {
        StreamReader archivo = null;
        
        try
        {
            archivo = new StreamReader("datos.txt");
            string contenido = archivo.ReadToEnd();
            Console.WriteLine(contenido);
        }
        catch (FileNotFoundException)
        {
            Console.WriteLine("No se encontró el archivo.");
        }
        catch (IOException)
        {
            Console.WriteLine("Error al leer el archivo.");
        }
        finally
        {
            // Cerramos el archivo incluso si hubo una excepción
            if (archivo != null)
            {
                archivo.Close();
                Console.WriteLine("Archivo cerrado correctamente.");
            }
        }
        
        Console.WriteLine("Programa finalizado.");
    }
}

En este ejemplo, el bloque finally garantiza que el archivo se cierre correctamente, incluso si ocurre un error durante la lectura. Esto es crucial para evitar fugas de recursos.

Patrón using como alternativa a finally

Para recursos que implementan la interfaz IDisposable (como archivos, conexiones a bases de datos, etc.), C# ofrece una alternativa más elegante al patrón try-finally: la instrucción using:

using System;
using System.IO;

class Program
{
    static void Main()
    {
        try
        {
            using (StreamReader archivo = new StreamReader("datos.txt"))
            {
                string contenido = archivo.ReadToEnd();
                Console.WriteLine(contenido);
            } // El archivo se cierra automáticamente al salir del bloque using
        }
        catch (FileNotFoundException)
        {
            Console.WriteLine("No se encontró el archivo.");
        }
        catch (IOException)
        {
            Console.WriteLine("Error al leer el archivo.");
        }
        
        Console.WriteLine("Programa finalizado.");
    }
}

La instrucción using garantiza que el método Dispose() del objeto se llame automáticamente al salir del bloque, incluso si ocurre una excepción. Esto equivale a usar un bloque finally para cerrar o liberar el recurso.

Combinación de try-catch-finally

Podemos combinar estos elementos según nuestras necesidades. Un bloque try puede tener:

  • Uno o más bloques catch
  • Un bloque finally (opcional)
  • Ambos (catch y finally)
using System;

class Program
{
    static void Main()
    {
        Console.WriteLine("Inicio del programa");
        
        try
        {
            Console.WriteLine("Dentro del bloque try");
            int[] numeros = { 1, 2, 3 };
            Console.WriteLine(numeros[5]); // Esto generará una excepción
        }
        catch (IndexOutOfRangeException)
        {
            Console.WriteLine("Error: Índice fuera de rango");
        }
        finally
        {
            Console.WriteLine("Este bloque siempre se ejecuta");
        }
        
        Console.WriteLine("Fin del programa");
    }
}

La salida de este programa será:

Inicio del programa
Dentro del bloque try
Error: Índice fuera de rango
Este bloque siempre se ejecuta
Fin del programa

Excepciones anidadas

También podemos tener bloques try-catch-finally anidados. Esto es útil cuando necesitamos manejar excepciones en diferentes niveles:

using System;

class Program
{
    static void Main()
    {
        try
        {
            Console.WriteLine("Bloque try externo");
            
            try
            {
                Console.WriteLine("Bloque try interno");
                throw new ArgumentException("Error de argumento");
            }
            catch (ArgumentException ex)
            {
                Console.WriteLine($"Catch interno: {ex.Message}");
                throw; // Re-lanzamos la excepción
            }
            finally
            {
                Console.WriteLine("Finally interno");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Catch externo: {ex.Message}");
        }
        finally
        {
            Console.WriteLine("Finally externo");
        }
    }
}

La salida será:

Bloque try externo
Bloque try interno
Catch interno: Error de argumento
Finally interno
Catch externo: Error de argumento
Finally externo

Observa cómo se ejecutan ambos bloques finally, y cómo la excepción re-lanzada es capturada por el catch externo.

Mejores prácticas

Al trabajar con múltiples catch y finally, considera estas recomendaciones:

  • Sé específico: Captura solo las excepciones que puedes manejar adecuadamente.
  • Ordena correctamente: Coloca las excepciones más específicas primero.
  • Usa finally para limpieza: Utiliza finally para liberar recursos, no para lógica de negocio.
  • Considera using: Para recursos IDisposable, prefiere la instrucción using cuando sea posible.
  • No atrapes todo silenciosamente: Evita catch vacíos que oculten errores sin manejarlos.
// Mal ejemplo (no hacer esto)
try
{
    // Código
}
catch (Exception)
{
    // Ignorar silenciosamente
}

// Mejor enfoque
try
{
    // Código
}
catch (Exception ex)
{
    // Registrar el error
    Console.WriteLine($"Error: {ex.Message}");
    // Posiblemente re-lanzar si no podemos manejarlo
    throw;
}

El manejo adecuado de excepciones con múltiples catch y finally te permitirá crear aplicaciones más robustas que puedan recuperarse de errores y liberar recursos correctamente, mejorando tanto la experiencia del usuario como la integridad de tu sistema.

Lanzar con throw

En C#, además de capturar excepciones, también podemos crearlas y lanzarlas explícitamente cuando detectamos situaciones de error en nuestro código. La palabra clave throw nos permite generar excepciones de forma controlada cuando identificamos condiciones que no deberían ocurrir en el flujo normal de nuestro programa.

Sintaxis básica de throw

La sintaxis para lanzar una excepción es muy sencilla:

throw new TipoDeExcepcion("Mensaje descriptivo del error");

Por ejemplo, si queremos lanzar una excepción cuando un parámetro no cumple con ciertos requisitos:

public void VerificarEdad(int edad)
{
    if (edad < 0)
    {
        throw new ArgumentException("La edad no puede ser negativa");
    }
    
    Console.WriteLine($"La edad {edad} es válida");
}

En este ejemplo, si alguien intenta verificar una edad negativa, el método lanzará una excepción en lugar de continuar con la ejecución normal.

Cuándo usar throw

El lanzamiento de excepciones es útil en varias situaciones:

  • Validación de parámetros: Cuando los argumentos de un método no cumplen con los requisitos esperados.
  • Estados imposibles: Cuando el programa llega a un estado que no debería ser posible.
  • Errores de lógica de negocio: Cuando se violan reglas específicas de la aplicación.
  • Propagación de errores: Cuando queremos que un error de bajo nivel se convierta en uno más significativo.

Veamos un ejemplo más completo:

using System;

class Calculadora
{
    public double Dividir(double numerador, double denominador)
    {
        if (denominador == 0)
        {
            throw new DivideByZeroException("No se puede dividir por cero");
        }
        
        return numerador / denominador;
    }
    
    public double CalcularRaizCuadrada(double numero)
    {
        if (numero < 0)
        {
            throw new ArgumentOutOfRangeException("numero", "No se puede calcular la raíz cuadrada de un número negativo");
        }
        
        return Math.Sqrt(numero);
    }
}

class Program
{
    static void Main()
    {
        Calculadora calc = new Calculadora();
        
        try
        {
            // Probamos con un caso válido
            double resultado1 = calc.Dividir(10, 2);
            Console.WriteLine($"10 / 2 = {resultado1}");
            
            // Probamos con un caso inválido
            double resultado2 = calc.Dividir(5, 0);
            Console.WriteLine($"5 / 0 = {resultado2}"); // Esta línea nunca se ejecutará
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
        
        try
        {
            // Probamos con un caso inválido
            double raiz = calc.CalcularRaizCuadrada(-4);
        }
        catch (ArgumentOutOfRangeException ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}

Tipos comunes de excepciones para lanzar

C# proporciona muchas clases de excepciones predefinidas que podemos utilizar según el tipo de error:

  • ArgumentException: Para argumentos inválidos en general.
  • ArgumentNullException: Cuando un argumento que no debería ser null lo es.
  • ArgumentOutOfRangeException: Cuando un argumento está fuera del rango permitido.
  • InvalidOperationException: Cuando una operación no es válida en el estado actual.
  • NotImplementedException: Para métodos que aún no están implementados.
  • NotSupportedException: Para operaciones que no son compatibles.
public void ProcesarDatos(string[] datos)
{
    if (datos == null)
    {
        throw new ArgumentNullException(nameof(datos), "El array de datos no puede ser null");
    }
    
    if (datos.Length == 0)
    {
        throw new ArgumentException("El array de datos no puede estar vacío", nameof(datos));
    }
    
    // Procesamiento normal...
}

Re-lanzamiento de excepciones

En ocasiones, queremos capturar una excepción, realizar alguna acción (como registrar el error) y luego volver a lanzarla para que sea manejada en un nivel superior. Hay dos formas de hacerlo:

  1. Re-lanzamiento simple: Mantiene la pila de llamadas original.
try
{
    // Código que puede generar una excepción
}
catch (Exception ex)
{
    // Registramos el error
    Console.WriteLine($"Se ha registrado un error: {ex.Message}");
    
    // Re-lanzamos la misma excepción manteniendo la pila de llamadas original
    throw;
}
  1. Creación de una nueva excepción: Crea una nueva excepción con información adicional.
try
{
    // Código que puede generar una excepción
}
catch (IOException ex)
{
    // Creamos una nueva excepción con la original como InnerException
    throw new ApplicationException("Error al procesar el archivo", ex);
}

La diferencia es importante:

  • throw; mantiene la pila de llamadas original, lo que facilita la depuración.
  • throw new Exception(...); crea una nueva pila de llamadas, pero permite añadir contexto adicional.

Creación de excepciones personalizadas

Cuando los tipos de excepciones predefinidos no son suficientes, podemos crear nuestras propias clases de excepciones:

using System;
using System.Runtime.Serialization;

[Serializable]
public class UsuarioNoEncontradoException : Exception
{
    public string Usuario { get; }
    
    public UsuarioNoEncontradoException() : base() { }
    
    public UsuarioNoEncontradoException(string message) : base(message) { }
    
    public UsuarioNoEncontradoException(string message, Exception innerException) 
        : base(message, innerException) { }
    
    public UsuarioNoEncontradoException(string message, string usuario) 
        : base(message)
    {
        Usuario = usuario;
    }
    
    // Constructor necesario para la serialización
    protected UsuarioNoEncontradoException(SerializationInfo info, StreamingContext context) 
        : base(info, context) { }
}

Y podemos usarla así:

public Usuario BuscarUsuario(string nombreUsuario)
{
    // Simulamos búsqueda en base de datos
    if (nombreUsuario == "admin")
    {
        return new Usuario { Nombre = "Administrador", Id = 1 };
    }
    
    throw new UsuarioNoEncontradoException(
        $"No se encontró el usuario con nombre '{nombreUsuario}'", 
        nombreUsuario);
}

Ejemplo práctico: Validación de datos

Veamos un ejemplo completo de cómo usar throw para validar datos de entrada:

using System;

class Producto
{
    private string _nombre;
    private decimal _precio;
    private int _stock;
    
    public string Nombre
    {
        get => _nombre;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentException("El nombre del producto no puede estar vacío");
            }
            _nombre = value;
        }
    }
    
    public decimal Precio
    {
        get => _precio;
        set
        {
            if (value < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), 
                    "El precio no puede ser negativo");
            }
            _precio = value;
        }
    }
    
    public int Stock
    {
        get => _stock;
        set
        {
            if (value < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), 
                    "El stock no puede ser negativo");
            }
            _stock = value;
        }
    }
    
    public void Vender(int cantidad)
    {
        if (cantidad <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(cantidad), 
                "La cantidad a vender debe ser mayor que cero");
        }
        
        if (cantidad > _stock)
        {
            throw new InvalidOperationException(
                $"No hay suficiente stock. Stock actual: {_stock}, Cantidad solicitada: {cantidad}");
        }
        
        _stock -= cantidad;
    }
}

class Program
{
    static void Main()
    {
        try
        {
            Producto laptop = new Producto();
            laptop.Nombre = "Laptop HP";
            laptop.Precio = 1200.50m;
            laptop.Stock = 10;
            
            Console.WriteLine($"Producto: {laptop.Nombre}, Precio: ${laptop.Precio}, Stock: {laptop.Stock}");
            
            // Intentamos vender más de lo que hay en stock
            laptop.Vender(15);
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine($"Error de argumento: {ex.Message}");
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine($"Error de operación: {ex.Message}");
        }
    }
}

Mejores prácticas al lanzar excepciones

Para usar throw de manera efectiva, considera estas recomendaciones:

  • Sé específico: Usa el tipo de excepción más específico para el error.
  • Mensajes claros: Incluye mensajes descriptivos que ayuden a entender y solucionar el problema.
  • Incluye detalles relevantes: Añade información contextual como nombres de parámetros o valores.
  • No abuses: Lanza excepciones solo para condiciones excepcionales, no para controlar el flujo normal.
  • Documenta: Indica en la documentación del método qué excepciones puede lanzar.
/// <summary>
/// Transfiere dinero entre dos cuentas.
/// </summary>
/// <param name="origen">Cuenta de origen</param>
/// <param name="destino">Cuenta de destino</param>
/// <param name="monto">Monto a transferir</param>
/// <exception cref="ArgumentNullException">Si alguna cuenta es null</exception>
/// <exception cref="ArgumentOutOfRangeException">Si el monto es negativo o cero</exception>
/// <exception cref="InvalidOperationException">Si la cuenta origen no tiene fondos suficientes</exception>
public void Transferir(Cuenta origen, Cuenta destino, decimal monto)
{
    if (origen == null)
        throw new ArgumentNullException(nameof(origen));
        
    if (destino == null)
        throw new ArgumentNullException(nameof(destino));
        
    if (monto <= 0)
        throw new ArgumentOutOfRangeException(nameof(monto), "El monto debe ser positivo");
        
    if (origen.Saldo < monto)
        throw new InvalidOperationException("Fondos insuficientes para realizar la transferencia");
        
    origen.Saldo -= monto;
    destino.Saldo += monto;
}

Throw vs. return de códigos de error

Antes de las excepciones, era común devolver códigos de error para indicar problemas. Comparemos ambos enfoques:

// Enfoque con códigos de error
public bool TryDividir(double a, double b, out double resultado, out string error)
{
    if (b == 0)
    {
        resultado = 0;
        error = "No se puede dividir por cero";
        return false;
    }
    
    resultado = a / b;
    error = null;
    return true;
}

// Uso:
if (TryDividir(10, 0, out double res, out string err))
{
    Console.WriteLine($"Resultado: {res}");
}
else
{
    Console.WriteLine($"Error: {err}");
}

// Enfoque con excepciones
public double Dividir(double a, double b)
{
    if (b == 0)
    {
        throw new DivideByZeroException("No se puede dividir por cero");
    }
    
    return a / b;
}

// Uso:
try
{
    double res = Dividir(10, 0);
    Console.WriteLine($"Resultado: {res}");
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

El enfoque con excepciones tiene varias ventajas:

  • Separación de responsabilidades: El código de éxito y el de error están claramente separados.
  • Propagación automática: Los errores se propagan automáticamente sin necesidad de verificar cada llamada.
  • Información detallada: Las excepciones contienen más información sobre el error.

El uso adecuado de throw te permitirá crear código más robusto y comunicar claramente las condiciones de error, mejorando tanto la calidad del código como la experiencia de desarrollo.

Aprende CSharp online

Otros ejercicios de programación de CSharp

Evalúa tus conocimientos de esta lección Excepciones con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Todas las lecciones de CSharp

Accede a todas las lecciones de CSharp y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A C#

Introducción Y Entorno

Creación De Proyecto C#

Introducción Y Entorno

Variables Y Constantes

Sintaxis

Tipos De Datos

Sintaxis

Operadores

Sintaxis

Control De Flujo

Sintaxis

Funciones

Sintaxis

Estructuras De Control Iterativo

Sintaxis

Interpolación De Strings

Sintaxis

Estructuras De Control Condicional

Sintaxis

Manejo De Valores Nulos

Sintaxis

Clases Y Encapsulación

Programación Orientada A Objetos

Objetos

Programación Orientada A Objetos

Constructores Y Destructores

Programación Orientada A Objetos

Herencia

Programación Orientada A Objetos

Polimorfismo

Programación Orientada A Objetos

Genéricos

Programación Orientada A Objetos

Métodos Virtuales Y Sobrecarga

Programación Orientada A Objetos

Clases Abstractas

Programación Orientada A Objetos

Interfaces

Programación Orientada A Objetos

Propiedades Y Encapsulación

Programación Orientada A Objetos

Métodos De Extensión

Programación Orientada A Objetos

Clases Y Objetos

Programación Orientada A Objetos

Clases Parciales

Programación Orientada A Objetos

Miembros Estáticos

Programación Orientada A Objetos

Tuplas Y Tipos Anónimos

Programación Orientada A Objetos

Arrays Y Listas

Colecciones Y Linq

Diccionarios

Colecciones Y Linq

Conjuntos, Colas Y Pilas

Colecciones Y Linq

Uso De Consultas Linq

Colecciones Y Linq

Linq Avanzado

Colecciones Y Linq

Colas Y Pilas

Colecciones Y Linq

Conjuntos

Colecciones Y Linq

Linq Básico

Colecciones Y Linq

Delegados Funcionales

Programación Funcional

Records

Programación Funcional

Expresiones Lambda

Programación Funcional

Linq Funcional

Programación Funcional

Fundamentos De La Programación Funcional

Programación Funcional

Pattern Matching

Programación Funcional

Testing Unitario Con Xunit

Testing

Excepciones

Excepciones

Delegados

Programación Asíncrona

Eventos

Programación Asíncrona

Lambdas

Programación Asíncrona

Uso De Async Y Await

Programación Asíncrona

Tareas

Programación Asíncrona

Accede GRATIS a CSharp y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la estructura básica y el flujo de ejecución de try-catch en C#.
  • Aprender a manejar múltiples tipos de excepciones con bloques catch específicos y filtros.
  • Entender el uso del bloque finally para liberar recursos y su relación con la instrucción using.
  • Saber cómo lanzar excepciones con throw y crear excepciones personalizadas.
  • Aplicar buenas prácticas en el manejo y lanzamiento de excepciones para escribir código robusto y mantenible.