Search⌘ K
AI Features

Try...Catch...Finally

Explore how to implement robust exception handling in C# using try, catch, and finally blocks. Learn to manage runtime errors like out-of-range array access, handle specific exceptions, and ensure resource cleanup with finally blocks. Understand required syntax rules and advanced patterns to write more reliable and maintainable code.

Program errors are unavoidable. They can happen because our application is trying to reach a server that is not responding, because we run out of computer memory, or because our program is trying to read a non-existent file. In any case, it is likely that an exception occurs.

Exceptions are program states where the application can no longer continue to run normally, so we have to address them.

Consider the following example. We create an array of size four, but we attempt to access the fifth element (which does not exist). This produces an exception.

C# 14.0
// Valid indexes: 0, 1, 2, 3
var numbers = new int[] { 1, 2, 3, 4 };
// We are trying to print the 5th element
// but the array holds only 4 elements
Console.WriteLine(numbers[4]);
  • Line 2: We initialize an integer array named numbers with four elements.

  • Line 6: We attempt to print the element at index 4. Since arrays are zero-indexed, the valid indices are 0 through 3. This line triggers a runtime error.

The result of execution is an unhandled exception of type IndexOutOfRangeException. This exception halts execution because the code tried to access an array index outside of its allocated bounds.

Exception handling in C#

The try, catch, and finally blocks allow us to run our code, handle an exception if it occurs, and perform cleanup operations.

try
{
// Code that may generate an exception
}
catch
{
// Actions to take in case an exception occurs
}
Syntax of the try-catch statement

When working with arrays, it is reasonable to expect that exceptions might occur. We use the try and catch blocks to prevent the program from crashing.

C# 14.0
try
{
var numbers = new int[] { 1, 2, 3, 4 };
Console.WriteLine(numbers[4]);
}
catch
{
Console.WriteLine("Exception occurred.");
}
  • Line 1: We start the try block. The program attempts to execute the code inside this block.

  • Line 4: This line triggers an exception because index 4 is out of bounds.

  • Line 6: The program immediately jumps to the catch block when the error occurs.

  • Line 8: We print a friendly message to the console instead of the program crashing.

Instead of terminating, our program executes without errors.

Flow of the try...catch...finally
Flow of the try...catch...finally

We can define specific catch blocks to handle different types of exceptions.

C# 14.0
try
{
var numbers = new int[] { 1, 2, 3, 4 };
Console.WriteLine(numbers[4]);
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("You went beyond the array's bounds.");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
  • Line 6: We specify IndexOutOfRangeException. If the error inside the try block matches this type, this block executes.

  • Line 10: This catch block uses the base Exception type. It acts as a fallback to catch any other errors that are not IndexOutOfRangeException.

  • Line 12: We access the Message property of the exception object ex to print the system’s error description.

The finally block

The finally block is optional, but it is useful when our code works with external resources and connections that must be closed after we are done working with them.

Note: If our code terminates with an exception while an external connection (such as a database connection) is open and we fail to close it, it will lead to memory leaks.

No matter what happens in our try and catch blocks, the finally block will run.

C# 14.0
try
{
var numbers = new int[] { 1, 2, 3, 4 };
Console.WriteLine(numbers[4]);
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("You went beyond the array's bounds.");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("Releasing the resources...");
}
  • Line 6: The specific IndexOutOfRangeException is caught here.

  • Line 14: The finally block executes regardless of whether an exception occurred or not.

  • Line 16: This code runs even if the try block finishes successfully or if a catch block handles an error. This ensures resources are always released.

Mandatory block requirements

C# enforces strict syntactical rules for these blocks:

  • Association with try: A catch or finally block cannot exist independently; they must immediately follow a try block.

  • Minimum requirement: We cannot omit both the catch and finally blocks. A try statement must be accompanied by at least one of them to be valid.

  • Specific order: When using all three, the sequence must always be try, followed by catch, and ending with finally.

The try-finally pattern

You do not always have to include a catch block. Sometimes, you want an exception to move up to a different part of the program (propagate), but you still need to ensure that resources (like a file or a database connection) are closed properly.

C# 14.0
try
{
Console.WriteLine("Attempting to open a system resource...");
// An error occurs here that we do not catch locally
throw new Exception("Critical Resource Failure!");
}
finally
{
// This code runs even though the error was not caught here
Console.WriteLine("Cleanup: Ensuring the resource is safely closed.");
}
  • Line 1: We initiate a try block to monitor code execution.

  • Line 5: We throw an exception manually. Since there is no catch block here, the exception is unhandled in this method and will “bubble up” to the caller.

  • Line 7: We define the finally block, which is guaranteed to run even if the program is about to crash or propagate an error.

  • Line 10: This cleanup code executes to prevent resource leaks before the exception moves up the call stack.

If we use try...finally without a catch block, the exception remains unhandled in the current location. Once the finally block finishes, the runtime propagates the error up the call stack to find a matching catch block in a parent method. If no handler is found anywhere in the program, the application will terminate.