Exception-handling statements - throw
, try-catch
, try-finally
, and try-catch-finally
You use the throw
and try
statements to work with exceptions. Use the throw
statement to throw an exception. Use the try
statement to catch and handle exceptions that might occur during execution of a code block.
The throw
statement
The throw
statement throws an exception:
if (shapeAmount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(shapeAmount), "Amount of shapes must be positive.");
}
In a throw e;
statement, the result of expression e
must be implicitly convertible to System.Exception.
You can use the built-in exception classes, for example, ArgumentOutOfRangeException or InvalidOperationException. .NET also provides the following helper methods to throw exceptions in certain conditions: ArgumentNullException.ThrowIfNull and ArgumentException.ThrowIfNullOrEmpty. You can also define your own exception classes that derive from System.Exception. For more information, see Creating and throwing exceptions.
Inside a catch
block, you can use a throw;
statement to re-throw the exception that is handled by the catch
block:
try
{
ProcessShapes(shapeAmount);
}
catch (Exception e)
{
LogError(e, "Shape processing failed.");
throw;
}
Note
throw;
preserves the original stack trace of the exception, which is stored in the Exception.StackTrace property. Opposite to that, throw e;
updates the StackTrace property of e
.
When an exception is thrown, the common language runtime (CLR) looks for the catch
block that can handle this exception. If the currently executed method doesn't contain such a catch
block, the CLR looks at the method that called the current method, and so on up the call stack. If no catch
block is found, the CLR terminates the executing thread. For more information, see the How exceptions are handled section of the C# language specification.
The throw
expression
You can also use throw
as an expression. This might be convenient in a number of cases, which include:
the conditional operator. The following example uses a
throw
expression to throw an ArgumentException when the passed arrayargs
is empty:string first = args.Length >= 1 ? args[0] : throw new ArgumentException("Please supply at least one argument.");
the null-coalescing operator. The following example uses a
throw
expression to throw an ArgumentNullException when the string to assign to a property isnull
:public string Name { get => name; set => name = value ?? throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null"); }
an expression-bodied lambda or method. The following example uses a
throw
expression to throw an InvalidCastException to indicate that a conversion to a DateTime value is not supported:DateTime ToDateTime(IFormatProvider provider) => throw new InvalidCastException("Conversion to a DateTime is not supported.");
The try
statement
You can use the try
statement in any of the following forms: try-catch
- to handle exceptions that might occur during execution of the code inside a try
block, try-finally
- to specify the code that is executed when control leaves the try
block, and try-catch-finally
- as a combination of the preceding two forms.
The try-catch
statement
Use the try-catch
statement to handle exceptions that might occur during execution of a code block. Place the code where an exception might occur inside a try
block. Use a catch clause to specify the base type of exceptions you want to handle in the corresponding catch
block:
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
You can provide several catch clauses:
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.");
}
When an exception occurs, catch clauses are examined in the specified order, from top to bottom. At maximum, only one catch
block is executed for any thrown exception. As the preceding example also shows, you can omit declaration of an exception variable and specify only the exception type in a catch clause. A catch clause without any specified exception type matches any exception and, if present, must be the last catch clause.
If you want to re-throw a caught exception, use the throw
statement, as the following example shows:
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e)
{
LogError(e, "Processing failed.");
throw;
}
Note
throw;
preserves the original stack trace of the exception, which is stored in the Exception.StackTrace property. Opposite to that, throw e;
updates the StackTrace property of e
.
A when
exception filter
Along with an exception type, you can also specify an exception filter that further examines an exception and decides if the corresponding catch
block handles that exception. An exception filter is a Boolean expression that follows the when
keyword, as the following example shows:
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}");
}
The preceding example uses an exception filter to provide a single catch
block to handle exceptions of two specified types.
You can provide several catch
clauses for the same exception type if they distinguish by exception filters. One of those clauses might have no exception filter. If such a clause exists, it must be the last of the clauses that specify that exception type.
If a catch
clause has an exception filter, it can specify the exception type that is the same as or less derived than an exception type of a catch
clause that appears after it. For example, if an exception filter is present, a catch (Exception e)
clause doesn't need to be the last clause.
Exceptions in async and iterator methods
If an exception occurs in an async function, it propagates to the caller of the function when you await the result of the function, as the following example shows:
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;
}
If an exception occurs in an iterator method, it propagates to the caller only when the iterator advances to the next element.
The try-finally
statement
In a try-finally
statement, the finally
block is executed when control leaves the try
block. Control might leave the try
block as a result of
- normal execution,
- execution of a jump statement (that is,
return
,break
,continue
, orgoto
), or - propagation of an exception out of the
try
block.
The following example uses the finally
block to reset the state of an object before control leaves the method:
public async Task HandleRequest(int itemId, CancellationToken ct)
{
Busy = true;
try
{
await ProcessAsync(itemId, ct);
}
finally
{
Busy = false;
}
}
You can also use the finally
block to clean up allocated resources used in the try
block.
Note
When the type of a resource implements the IDisposable or IAsyncDisposable interface, consider the using
statement. The using
statement ensures that acquired resources are disposed when control leaves the using
statement. The compiler transforms a using
statement into a try-finally
statement.
Execution of the finally
block depends on whether the operating system chooses to trigger an exception unwind operation. The only cases where finally
blocks aren't executed involve immediate termination of a program. For example, such a termination might happen because of the Environment.FailFast call or an OverflowException or InvalidProgramException exception. Most operating systems perform a reasonable resource clean-up as part of stopping and unloading the process.
The try-catch-finally
statement
You use a try-catch-finally
statement both to handle exceptions that might occur during execution of the try
block and specify the code that must be executed when control leaves the try
statement:
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;
}
}
When an exception is handled by a catch
block, the finally
block is executed after execution of that catch
block (even if another exception occurs during execution of the catch
block). For information about catch
and finally
blocks, see The try-catch
statement and The try-finally
statement sections, respectively.
C# language specification
For more information, see the following sections of the C# language specification: