Do you want to know how to use C# try catch?

In this article, we will learn how to use C# try catch statements, exceptions, and exception handling. We will learn how to catch multiple exception types and look over C# try finally statement. Before we end, we will look over stack trace and see some best practices when it comes to exception handling. Enough chit-chat, let’s get to it!

What is an exception?

Imagine you are writing an application that is reading a list of files from Azure Blob Storage. After reading half of the files, the connection to the cloud interrupts. At this point, your application will crash. Here is where exceptions and exception handling comes into place.

Think of an exception as a notification that something has interrupted the normal execution of the program. Exceptions allow us to detect and react to unexpected events during execution. When the program throws an exception, the execution flow pauses and handles the exception.

Exception handling is a mechanism that allows us to catch and throw exceptions. In Object Oriented Programming, exceptions are centralizing the processing of errors. In effect, exceptions are classes. We inherited these classes and this allows us to have an exceptions hierarchy. Use exceptions for managing error situations or unexpected events during the program execution. Here is an example of code that throws an exception:

File.ReadAllText("InvalidFilePath.txt");
Console.WriteLine("File read successfully!");

The program execution is trying to read the file InvalidFilePath.txt and this file is not found. Thus it throws an exception:

Unhandled exception. System.IO.FileNotFoundException: Could not find file '...InvalidFilePath.txt'.

Please bear in mind that once the exception throws, the program execution interrupts. In our example, the second line will not execute and the program will stop. Let’s see what can we do in these cases and how can we catch and handle these exceptions.

C# try catch

The best tool at your disposal for catching exceptions is the C# try catch statement. The try-catch statement consists of two blocks. The first one is the try block and the second one is the catch block. In the try block, we put the code that could throw exceptions. In the catch block, we put the code for handling the exception. This is what a simple C# try catch statement looks like:

try
{
    // code that might throw an exception
}
catch
{
    // exception handling
}

Now let’s see what our example from above looks like when using a try-catch statement.

try
{
    File.ReadAllText("InvalidFilePath.txt");
    Console.WriteLine("File read successfully!");
}
catch (Exception exception)
{
    Console.WriteLine($"Unable to read file. Exception: {exception.Message}");
}

You can see in the example above that we have placed our code inside the try block and in the catch block we handle the exception. In order to have details about the exception, we use the exception argument.

C# try catch multiple exceptions

In some cases, you will need something more to handle multiple types of exceptions. For this, we can use multiple catch blocks, one block for each type of exception. These are also called exception filters.

Now let’s presume we want to read all the lines in a file and present to the user the content of the second line. Here is how our code would look:

try
{
    var fileLines = File.ReadAllLines("InvalidFilePath.txt");
    Console.WriteLine($"Second line of the file: {fileLines[1]}");
}
catch (FileNotFoundException fileNotFoundException)
{
    Console.WriteLine($"File not found! Exception: {fileNotFoundException.Message}");
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("Unable to read second the line of the file!");
}
catch (Exception exception)
{
    Console.WriteLine($"Unexpected exception occurred. Exception message: {exception.Message}");
}

You can see we are filtering the exception based on the type. If we are unable to read the file we handle FileNotFoundException. If we manage to read the file and cannot find the second line in the file we handle IndexOutOfRangeException. In this catch block, you can see we don’t declare an actual variable for the exception because we don’t need special handling for it. In the last step, we handle every other exception and present to the user the exception message.

C# try finally

Besides the try block and the catch block we can use the finally block. The finally block will execute always, regardless if the program executes the try block or the catch block. This is how try-catch-finally would translate into a diagram:

Try catch finally

You can see that if the exception throws within the try block, the execution will continue within the catch block. In the catch block, we handle the exception and then execute the finally block. If there is no exception thrown within the try block, the finally block executes as well.

Now let’s look over an example:

// create connection
try
{
    // do some operations with the database
}
catch (Exception exception)
{
    // handle the errors
}
finally
{
    // close the connection
}

In the above example, we are declaring a connection before the try block. Inside the try block, we are connecting to the database or whatever the connection is to and we are executing the business rules. In the catch block, we are handling the exception. Then, regardless if we have an exception thrown or not, we are closing the connection in the finally block. In the finally block we usually put code for releasing the resources used.

Besides the try-catch-finally syntax, you can use the try block only with finally block, without the catch block. This is how it looks:

try
{
    // do something
}
finally
{
    // always execute this code
}

Stack trace

The stack trace is the place where you find detailed information about the exception. Here we can find valuable information about the place where the exception occurred. The stack trace is more technical and is very helpful when debugging. This is what the stack trace looks like for our first example of exceptions:

Unhandled exception. System.IO.FileNotFoundException: Could not find file '...InvalidFilePath.txt'.
File name: '...InvalidFilePath.txt'
   at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
   at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
   at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
   at System.IO.Strategies.FileStreamHelpers.ChooseStrategy(FileStream fileStream, String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Int64 preallocationSize)
   at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize)
   at System.IO.File.InternalReadAllLines(String path, Encoding encoding)
   at System.IO.File.ReadAllLines(String path)
   at Program.<Main>$(String[] args) in ...Program.cs:line 1

As seen above, the stack trace contains information about the name of the exception class, the error message, and the call stack. In the call stack, we have one line for each method call. In our example, we can read the call stack from the bottom up. We can see the line number where the exception throws for our Program.cs class. The call stack provides the line number for all classes that were compiled with debug information. You can find the debug information in the .pdb files.

Best practices

Here are some recommendations when handling exceptions:

  • Prefer exceptions rather than returning error codes.
  • Extract try-catch blocks to separate methods – this makes the code easier to read.
  • Provide context with the exceptions you throw – create your descriptive messages for your exceptions and pass them along.
  • Define exception classes as needed by the caller.
  • When handling the exception, don’t return null, prefer throwing the exception or returning an object.

I hope I was able to give you enough information to help you handle the exceptions in your code. If you have any questions, or just want to give me some feedback, please use the comments section below.