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 siguientes métodos asistentes 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 matrizargs
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 esnull
: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
ogoto
), 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
.
La ejecución del bloque finally
depende de si el sistema operativo decide desencadenar una operación de desenredo de la excepción. 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#: