Редагувати

Поділитися через


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 array args 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 is null:

    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, or goto), 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:

See also