Saltar al pie de página
Iron Academy Logo
Aprender C#
Aprender C#

Otras categorías

C# Exception Handling

Tim Corey
59m 46s

La gestión de excepciones es un aspecto crucial del desarrollo de aplicaciones robustas. El vídeo de Tim Corey sobre "Manejo de excepciones en C# - Cuándo atraparlas, dónde atraparlas y cómo atraparlas", ofrece una explicación detallada de qué son las excepciones, cómo manejarlas y dónde manejarlas.

El objetivo de este artículo es explicar el manejo de excepciones en C# utilizando el vídeo de Tim Corey. Se trata de una potente función que permite a los desarrolladores gestionar los errores y las condiciones excepcionales que surgen durante la ejecución de un programa. Mediante los bloques try, catch y finally, C# ofrece una forma estructurada de gestionar errores en tiempo de ejecución, registrar excepciones y mantener el flujo del programa.

Introducción

Tim comienza explicando que muchos desarrolladores tienen una visión incorrecta de las excepciones y su gestión. Destaca la importancia de entender qué son las excepciones y dónde y cómo manejarlas adecuadamente para crear aplicaciones más robustas.

Construcción de una aplicación de consola de demostración

Tim crea una aplicación de consola en Visual Studio 2017 para demostrar el manejo de excepciones. Recomienda utilizar aplicaciones de consola para probar nuevos temas, ya que requieren una configuración mínima y es fácil trabajar con ellas.

using System;

namespace ExceptionsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // Placeholder for input and output operations
            Console.ReadLine();
        }
    }
}
using System;

namespace ExceptionsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // Placeholder for input and output operations
            Console.ReadLine();
        }
    }
}

Creación de una biblioteca de clases

Tim añade una biblioteca de clases a la solución para simular un escenario real en el que diferentes métodos se llaman entre sí.

Csharp Exception Handling 1 related to Creación de una biblioteca de clases

Elimina la clase por defecto y crea una nueva clase llamada DemoCode.

public class DemoCode
{
    // Method to retrieve a number based on the provided position
    public int GetNumber(int position)
    {
        int[] numbers = { 1, 4, 7, 2 };
        return numbers[position];
    }

    // Intermediate method calls GetNumber
    public int ParentMethod(int position)
    {
        return GetNumber(position);
    }

    // Top-level method calls ParentMethod
    public int GrandparentMethod(int position)
    {
        return ParentMethod(position);
    }
}
public class DemoCode
{
    // Method to retrieve a number based on the provided position
    public int GetNumber(int position)
    {
        int[] numbers = { 1, 4, 7, 2 };
        return numbers[position];
    }

    // Intermediate method calls GetNumber
    public int ParentMethod(int position)
    {
        return GetNumber(position);
    }

    // Top-level method calls ParentMethod
    public int GrandparentMethod(int position)
    {
        return ParentMethod(position);
    }
}

La clase DemoCode contiene métodos que se llaman entre sí, recuperando en última instancia un número de un array basado en la posición dada.

Simulación de una excepción

Tim explica que la aplicación está pensada para demostrar fallos más que éxitos. Introduce una excepción de fuera de límites al pasar una posición no válida al GrandparentMethod.

DemoCode demo = new DemoCode();
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
DemoCode demo = new DemoCode();
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");

Si se ejecuta el código anterior con una posición no válida, se produce una IndexOutOfRangeException. Tim muestra cómo el depurador de Visual Studio resalta el problema y proporciona información detallada sobre la excepción.

Cómo NO usar try-catch

Tim explica un error común que cometen los desarrolladores cuando aprenden por primera vez sobre los bloques try-catch. A menudo, los desarrolladores envuelven todo el bloque de código en el que esperan que se produzca una excepción, lo que puede dar lugar a una gestión inadecuada.

try
{
    int output = 0;
    output = numbers[position];
    return output;
}
catch (Exception ex)
{
    // Avoid returning default values that can mask the problem
    return 0;
}
try
{
    int output = 0;
    output = numbers[position];
    return output;
}
catch (Exception ex)
{
    // Avoid returning default values that can mask the problem
    return 0;
}

Tim destaca que este enfoque es problemático porque oculta la excepción y continúa la ejecución con suposiciones incorrectas. Por ejemplo, devolver 0 como valor por defecto puede no ser apropiado y causar más problemas.

Manejo correcto de excepciones

Tim subraya que las excepciones proporcionan información crítica sobre estados inesperados de la aplicación. Si la aplicación continúa en ese estado sin un tratamiento adecuado, puede dar lugar a más errores y corrupción de datos.

En lugar de tragarse las excepciones, es esencial manejarlas adecuadamente. He aquí un enfoque mejor:

try
{
    return numbers[position];
}
catch (Exception ex)
{
    // Log the exception or handle it appropriately
    Console.WriteLine(ex.Message);
    throw; // Re-throw the exception to be handled by a higher-level handler
}
try
{
    return numbers[position];
}
catch (Exception ex)
{
    // Log the exception or handle it appropriately
    Console.WriteLine(ex.Message);
    throw; // Re-throw the exception to be handled by a higher-level handler
}

Al volver a lanzar la excepción, te aseguras de que el problema se propague y pueda tratarse en un nivel superior si es necesario.

Proporcionar información útil al usuario

Tim explica que, aunque algunas excepciones pueden gestionarse con elegancia sin bloquear la aplicación, es importante proporcionar información útil al usuario. Por ejemplo, mostrando un cuadro de mensaje o una notificación con la opción de reintentar la operación.

Más información útil: StackTrace

Tim muestra cómo utilizar la propiedad StackTrace del objeto de excepción para obtener información detallada sobre dónde se ha producido la excepción. Esto incluye la clase, el método y el número de línea, lo que es muy valioso para la depuración.

try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.StackTrace);
    throw;
}
try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.StackTrace);
    throw;
}

La propiedad StackTrace proporciona una traza completa de la pila de llamadas, lo que ayuda a los desarrolladores a localizar exactamente el problema.

Colocación correcta de try-catch

Tim explica que el manejo adecuado de las excepciones no consiste únicamente en capturarlas, sino también en saber dónde colocar los bloques try-catch. La clave está en situar los bloques try-catch en un nivel en el que se disponga de contexto suficiente para gestionar la excepción adecuadamente.

Ejemplo de colocación incorrecta

Colocar un bloque try-catch en lo más profundo de la pila de llamadas a menudo no permite gestionar la excepción con eficacia porque se carece del contexto de las operaciones de nivel superior.

// Deep level exception handling (not ideal)
try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw;
}
// Deep level exception handling (not ideal)
try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw;
}

Ejemplo de colocación correcta

Colocar el bloque try-catch en el nivel superior, como en la interfaz de usuario o en el punto de entrada de la aplicación, permite gestionar las excepciones con el contexto completo de la operación.

try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

De este modo, podrá ofrecer mensajes más informativos al usuario y decidir si la aplicación puede seguir ejecutándose o si debe finalizar.

Información de seguimiento de pila

Tim hace hincapié en la importancia de la información de seguimiento de pila a la hora de diagnosticar excepciones. El seguimiento de la pila proporciona un historial detallado de las llamadas, mostrando dónde se produjo la excepción y la cadena de llamadas a métodos que condujeron a ella.

try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

El resultado proporciona la ubicación exacta de la excepción y la ruta seguida a través del código, lo que facilita la depuración y la solución del problema.

Demostración de lógica de manejo

Tim demuestra cómo manejar la lógica en el nivel adecuado. Por ejemplo, si un método es responsable de abrir y cerrar una conexión a una base de datos, debe manejar excepciones para garantizar que los recursos se gestionan correctamente.

public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        Console.WriteLine("Close database connection");
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Ensure the exception is propagated
    }
}
public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        Console.WriteLine("Close database connection");
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Ensure the exception is propagated
    }
}

En este ejemplo, si se produce una excepción, la conexión a la base de datos no se cierra correctamente, lo que puede provocar una fuga de recursos. Añadiendo un bloque try-catch, puede asegurarse de que la conexión se cierra incluso si se produce una excepción.

Utilización del bloque final

Tim presenta el bloque finally, que garantiza que cierto código se ejecute independientemente de que se produzca una excepción. Esto es particularmente útil para la limpieza de recursos, como el cierre de conexiones de bases de datos.

public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Re-throw the exception to ensure it's handled by a higher-level handler
    }
    finally
    {
        Console.WriteLine("Close database connection");
    }
}
public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Re-throw the exception to ensure it's handled by a higher-level handler
    }
    finally
    {
        Console.WriteLine("Close database connection");
    }
}

El bloque finally se ejecuta después de los bloques try y catch, asegurando que la conexión se cierra incluso si se lanza una excepción.

La declaración de lanzamiento

Tim explica la importancia de volver a lanzar excepciones para pasarlas a la pila de llamadas. De este modo, los gestores de nivel superior podrán procesar las excepciones adecuadamente.

catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw; // Re-throws the exception to be handled by the calling method
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw; // Re-throws the exception to be handled by the calling method
}

Volver a lanzar la excepción con throw; garantiza que se conserve el seguimiento completo de la pila, lo que proporciona un contexto valioso para la depuración.

Bombear correctamente las excepciones

Tim demuestra cómo las excepciones burbujean a través de la pila de llamadas. Cada método comprueba si hay un bloque try-catch y, o bien maneja la excepción, o bien se la pasa a quien la llama.

try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

En este ejemplo, el GrandparentMethod captura la excepción, la registra y la vuelve a lanzar. A continuación, el bloque try-catch de nivel superior de la aplicación de consola gestiona la excepción y muestra el mensaje de error y el seguimiento de la pila.

Errores comunes en el manejo de excepciones

Tim destaca varios errores comunes que cometen los desarrolladores al gestionar excepciones:

  1. Usando throw ex;:

    • Reescribir el stack trace y perder un contexto valioso.

    • Ejemplo:
      catch (Exception ex)
      {
      // Incorrect
      throw ex; // Rewrites stack trace
      }
      catch (Exception ex)
      {
      // Incorrect
      throw ex; // Rewrites stack trace
      }
  2. Lanzamiento de una nueva excepción:

    • Creación de una nueva excepción con un mensaje personalizado, pero pérdida del seguimiento de pila original.

    • Ejemplo:
      catch (Exception ex)
      {
      // Incorrect
      throw new Exception("I blew up");
      }
      catch (Exception ex)
      {
      // Incorrect
      throw new Exception("I blew up");
      }

Crear una nueva excepción sin perder el rastro de pila original

Tim explica cómo crear una nueva excepción conservando el rastro de pila original. Esto puede ser útil cuando se desea proporcionar un mensaje de error más significativo o un tipo de excepción diferente, pero conservando el contexto del error original.

catch (Exception ex)
{
    throw new ArgumentException("You passed in bad data", ex);
}
catch (Exception ex)
{
    throw new ArgumentException("You passed in bad data", ex);
}

Al pasar la excepción original (ex) como excepción interna, se mantiene el rastro de pila original, que es crucial para la depuración.

Cómo conservar la información de seguimiento de pila

Tim muestra cómo acceder al mensaje de la excepción original y al seguimiento de la pila al crear una nueva excepción.

catch (Exception ex)
{
    Console.WriteLine("You passed in bad data");
    Console.WriteLine(ex.StackTrace);
    throw new ArgumentException("You passed in bad data", ex);
}
catch (Exception ex)
{
    Console.WriteLine("You passed in bad data");
    Console.WriteLine(ex.StackTrace);
    throw new ArgumentException("You passed in bad data", ex);
}

Esto garantiza que la excepción lanzada a la pila contenga tanto el nuevo mensaje como los detalles de la excepción original.

Looping a través de excepciones internas

Tim proporciona un método para recorrer en bucle todas las excepciones internas y extraer sus mensajes y rastros de pila.

catch (Exception ex)
{
    Exception inner = ex;
    while (inner != null)
    {
        Console.WriteLine(inner.StackTrace);
        inner = inner.InnerException;
    }
    throw;
}
catch (Exception ex)
{
    Exception inner = ex;
    while (inner != null)
    {
        Console.WriteLine(inner.StackTrace);
        inner = inner.InnerException;
    }
    throw;
}

Este bucle itera a través de cada excepción interna, imprimiendo su rastro de pila, asegurándose de que se tienen en cuenta todas las capas de excepciones.

Cómo tratar las excepciones de forma diferente

Tim explica cómo manejar distintos tipos de excepciones utilizando varios bloques catch. Esto permite un tratamiento específico basado en el tipo de excepción.

try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

En este ejemplo, la excepción ArgumentException se gestiona específicamente imprimiendo un mensaje personalizado, mientras que todas las demás excepciones se devuelven a un gestor general que imprime el mensaje de excepción y el seguimiento de la pila.

Csharp Exception Handling 2 related to Cómo tratar las excepciones de forma diferente

Importancia del orden en los bloques de captura múltiple

Tim hace hincapié en la importancia del orden cuando se utilizan varios bloques de captura. Las excepciones más específicas deben capturarse en primer lugar, seguidas de las excepciones más generales.

try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

Si aparece un bloque de captura más general antes que uno específico, este último capturará todas las excepciones y nunca se llegará al bloque de captura específico, lo que provocará errores de compilación.

Conclusión

La guía avanzada de Tim Corey video sobre el manejo de excepciones en C# cubre técnicas críticas para crear nuevas excepciones, preservar las trazas de pila y utilizar múltiples bloques catch de forma eficaz. Siguiendo sus mejores prácticas, los desarrolladores pueden crear aplicaciones robustas que manejen las excepciones con elegancia y proporcionen valiosa información de depuración.

Hero Worlddot related to C# Exception Handling
Hero Affiliate related to C# Exception Handling

Gana más compartiendo lo que te gusta

¿Creas contenidos para desarrolladores que trabajan con .NET, C#, Java, Python o Node.js? ¡Convierte tu experiencia en un ingreso extra!

Equipo de soporte de Iron

Estamos disponibles online las 24 horas, 5 días a la semana.
Chat
Email
Llámame