Instrucciones de control de excepciones: throw, try-catch, try-finally y try-catch-finally

Las instrucciones throw y try se usan para trabajar con excepciones. Use la instrucción throw para producir una excepción. Use la instrucción try para detectar y controlar las excepciones que pueden producirse durante la ejecución de un bloque de código.

Instrucción throw

La instrucción throw produce una excepción:

if (shapeAmount <= 0)
{
    throw new ArgumentOutOfRangeException(nameof(shapeAmount), "Amount of shapes must be positive.");
}

En una instrucción throw e;, el resultado de la expresión e debe poderse convertir implícitamente a System.Exception.

Puede usar las clases de excepción integradas, por ejemplo, ArgumentOutOfRangeException o InvalidOperationException. .NET también proporciona los métodos auxiliares para producir excepciones en determinadas condiciones: ArgumentNullException.ThrowIfNull y ArgumentException.ThrowIfNullOrEmpty. También puede definir sus propias clases de excepción que se derivan de System.Exception. Para obtener más información, consulte Creación y producción de excepciones.

Dentro de un bloque catch, puede usar una instrucción throw; para volver a iniciar la excepción que controla el bloque catch:

try
{
    ProcessShapes(shapeAmount);
}
catch (Exception e)
{
    LogError(e, "Shape processing failed.");
    throw;
}

Nota

throw; conserva el seguimiento de pila original de la excepción, que se almacena en la propiedad Exception.StackTrace. Por el contrario, throw e; actualiza la propiedad StackTrace de e.

Cuando se produce una excepción, Common Language Runtime (CLR) busca el bloque catch que pueda controlar esta excepción. Si el método ejecutado actualmente no contiene un bloque catch, CLR busca el método que llamó el método actual, y así sucesivamente hasta la pila de llamadas. Si no se encuentra ningún bloque catch, CLR finaliza el subproceso en ejecución. Para obtener más información, consulte la sección Control de las excepciones) de la especificación del lenguaje C#.

La expresión throw

También puede usar throw como expresión. Esto puede resultar conveniente en varios casos, entre los que se incluyen:

  • El operador condicional. En el ejemplo siguiente se usa una expresión throw para iniciar ArgumentException cuando la matriz args pasada está vacía:

    string first = args.Length >= 1 
        ? args[0]
        : throw new ArgumentException("Please supply at least one argument.");
    
  • El operador de uso combinado de NULL. En el ejemplo siguiente se usa una expresión throw para iniciar ArgumentNullException cuando la cadena que se va a asignar a una propiedad es null:

    public string Name
    {
        get => name;
        set => name = value ??
            throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null");
    }
    
  • Un método o lambda con forma de expresión. En el ejemplo siguiente se usa una expresión throw para iniciar InvalidCastException para indicar que no se admite una conversión a un valor DateTime:

    DateTime ToDateTime(IFormatProvider provider) =>
             throw new InvalidCastException("Conversion to a DateTime is not supported.");
    

Instrucción try

Puede usar la instrucción try en cualquiera de las formas siguientes: try-catch - para controlar las excepciones que pueden producirse durante la ejecución del código dentro de un bloque try, try-finally - para especificar el código que se ejecuta cuando el control sale del bloque try y try-catch-finally - como una combinación de los dos formatos anteriores.

Instrucción try-catch

Use la instrucción try-catch para controlar las excepciones que pueden producirse durante la ejecución de un bloque de código. Coloque el código donde se puede producir una excepción dentro de un bloque try. Use una cláusula catch para especificar el tipo base de excepciones que desea controlar en el bloque catch correspondiente:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}

Puede proporcionar varias cláusulas catch:

try
{
    var result = await ProcessAsync(-3, 4, cancellationToken);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}
catch (OperationCanceledException)
{
    Console.WriteLine("Processing is cancelled.");
}

Cuando se produce una excepción, las cláusulas catch se examinan en el orden especificado, de arriba abajo. Como máximo, solo se ejecuta un bloque catch para cualquier excepción iniciada. Como también se muestra en el ejemplo anterior, puede omitir la declaración de una variable de excepción y especificar solo el tipo de excepción en una cláusula catch. Una cláusula catch sin ningún tipo de excepción especificado coincide con cualquier excepción y, si está presente, debe ser la última cláusula catch.

Si desea volver a iniciar una excepción detectada, use la instrucción throw, como se muestra en el ejemplo siguiente:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e)
{
    LogError(e, "Processing failed.");
    throw;
}

Nota

throw; conserva el seguimiento de pila original de la excepción, que se almacena en la propiedad Exception.StackTrace. Por el contrario, throw e; actualiza la propiedad StackTrace de e.

Un filtro de excepción when

Junto con un tipo de excepción, también puede especificar un filtro de excepción que examine aún más una excepción y decida si el bloque correspondiente catch controla esa excepción. Un filtro de excepción es una expresión booleana que sigue a la palabra clave when, como se muestra en el ejemplo siguiente:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e) when (e is ArgumentException || e is DivideByZeroException)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}

En el ejemplo anterior se usa un filtro de excepción para proporcionar un único bloque catch para controlar las excepciones de dos tipos especificados.

Puede proporcionar varias cláusulas catch para el mismo tipo de excepción si distinguen por filtros de excepción. Una de esas cláusulas podría no tener ningún filtro de excepción. Si existe dicha cláusula, debe ser la última de las cláusulas que especifican ese tipo de excepción.

Si una cláusula catch tiene un filtro de excepción, puede especificar el tipo de excepción que es igual o menos derivado que un tipo de excepción de una cláusula catch que aparece después de ella. Por ejemplo, si hay un filtro de excepción, no es necesario que una cláusula catch (Exception e) sea la última.

Excepciones en métodos asincrónicos e iteradores

Si se produce una excepción en una función asincrónica, se propaga al autor de la llamada de la función cuando espera el resultado de la función, como se muestra en el ejemplo siguiente:

public static async Task Run()
{
    try
    {
        Task<int> processing = ProcessAsync(-1);
        Console.WriteLine("Launched processing.");

        int result = await processing;
        Console.WriteLine($"Result: {result}.");
    }
    catch (ArgumentException e)
    {
        Console.WriteLine($"Processing failed: {e.Message}");
    }
    // Output:
    // Launched processing.
    // Processing failed: Input must be non-negative. (Parameter 'input')
}

private static async Task<int> ProcessAsync(int input)
{
    if (input < 0)
    {
        throw new ArgumentOutOfRangeException(nameof(input), "Input must be non-negative.");
    }

    await Task.Delay(500);
    return input;
}

Si se produce una excepción en un método de iterador, se propaga al autor de la llamada solo cuando el iterador avanza al siguiente elemento.

Instrucción try-finally

En una instrucción try-finally, el bloque finally se ejecuta cuando el control sale del bloque try. El control puede dejar el bloque try como resultado de una

  • ejecución normal,
  • ejecución de una instrucción de salto (es decir, return, break, continue o goto), o
  • propagación de una excepción fuera del bloque try.

En el ejemplo siguiente se usa el bloque finally para restablecer el estado de un objeto antes de que el control deje el método:

public async Task HandleRequest(int itemId, CancellationToken ct)
{
    Busy = true;

    try
    {
        await ProcessAsync(itemId, ct);
    }
    finally
    {
        Busy = false;
    }
}

También puede usar el bloque finally para limpiar los recursos asignados usados en el bloque try.

Nota

Cuando el tipo de un recurso implementa la interfaz IDisposable o IAsyncDisposable, tenga en cuenta la instrucción using. La instrucción using garantiza que los recursos adquiridos se eliminen cuando el control salga de la instrucción using. El compilador transforma una instrucción using en una instrucción try-finally.

En casi todos los casos se ejecutan bloques finally. Los únicos casos en los que los bloques finally no se ejecutan implican la finalización inmediata de un programa. Por ejemplo, esta terminación puede producirse debido a la llamada Environment.FailFast o a una excepción OverflowException o InvalidProgramException. La mayoría de los sistemas operativos realizan una limpieza de recursos razonable como parte de la detención y descarga del proceso.

Instrucción try-catch-finally

Use una instrucción try-catch-finally para controlar las excepciones que pueden producirse durante la ejecución del bloque try y especificar el código que se debe ejecutar cuando el control sale de la instrucción try:

public async Task ProcessRequest(int itemId, CancellationToken ct)
{
    Busy = true;

    try
    {
        await ProcessAsync(itemId, ct);
    }
    catch (Exception e) when (e is not OperationCanceledException)
    {
        LogError(e, $"Failed to process request for item ID {itemId}.");
        throw;
    }
    finally
    {
        Busy = false;
    }

}

Cuando un bloque catch controla una excepción, el bloque finally se ejecuta después de la ejecución de ese bloque catch (incluso si se produce otra excepción durante la ejecución del bloque catch). Para obtener información sobre los bloques catch y finally, vea las secciones Instrucción try-catch e Instrucción try-finally, respectivamente.

Especificación del lenguaje C#

Para más información, vea las secciones siguientes de la Especificación del lenguaje C#:

Consulte también