Sometimes our application crashes due to unexpected errors and exceptions that occur during the program execution. So, in this article, we are going to discuss the try-catch block in C# and learn how to handle those exceptions.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

What is Try-Catch in C#

By using a try-catch block in C#, we are handling exceptions that could happen in our code. Exceptions are problems in our application that are predictable while errors are not. Possible exceptions include opening an unreachable file, manipulating a null object, and dividing a number by zero. When these exceptions occur, the system will raise them.

Sometimes, our end-user may trigger exceptions in our application based on their actions. Other times, it could be due to the environmental context our code is running on. Whatever the cause, such exceptions may crash our application if not properly handled using try-catch.

The try block should contain any suspicious code that may likely produce an exception. We should take note that the C# compiler will flag any try block with no catch or finally block. Similarly, the catch block contains any action or operation we want to execute when the code in the try block raises an exception. Logging is an example of such action. For instance, when receiving arguments with a method we may want to log and throw an exception if the expected values are null. We should use a specific exception (ArgumentException) rather than the global Exception object for our catch handler.

Try-Catch Flow

A try-catch consists of a try block followed by one or more catch clauses, which specify handlers for different exceptions. The CLR (Common Language Runtime) is responsible for handling exceptions in a running application.

The CLR takes several steps when an exception occurs. First, it checks to see if the method that raised the exception has a suitable catch handler. If so, the flow of execution jumps to the handler, it executes the code in the block, and then the program continues its execution.

But if there is no  catch block, the CLR checks if the method that called the current method on the call stack has a suitable handler. The CLR does this for all methods on the call stack till it gets a suitable handler. When the CLR reaches the end of the call stack without finding a suitable handler, it will display an unhandled exception message to the user and terminate the application.

When should We Use Try and Catch?

Basically, we should use try-catch to prevent our application from crashing due to exceptions. In addition, we can use try-catch to display a custom message to the end-user based on some invalid input, unexpected inputs, or user actions. Going one step further, some third-party applications don’t handle exceptions so it is the job of the developer to handle any exceptions raised from its usage.

The Basic Try and Catch Block

Let’s take a look at how we can use a simple try-catch block in C#:

try
{
    // the code here that may raise exceptions
}
catch
{
    // handle an exception here
}

A try block can have multiple catch blocks to handle the different types of exceptions that might occur during the program execution.

Specific exceptions should come first:

try
{
    var counter  = new int[5];
    counter [10] = 12;
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine("{0} First exception caught.", ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("{0} Last exception caught.", ex.Message);
}

A catch block can also throw a new exception or re-throw the current exception. The difference between a new exception and a re-thrown one lies in the preservation of the stack trace. Preservation of the stack trace provides means to trace the call to the line number in the method where the exception occurs.  On one hand, a re-thrown exception preserves the original stack trace of the exception while a new exception doesn’t.

An exception is thrown using the throw keyword, which may or may not be followed by a new instance of a class that derives from System.Exception:

try
{
    int[] counter = null;
    throw new NullReferenceException("Undefined array");
}
catch (NullReferenceException ex) 
{ 
    throw; 
}
catch (Exception ex) 
{
    throw new ApplicationException("Exception thrown");
}

The first catch block handles any null exception raised in the try block. When the try block raises a null exception, the CLR goes to the first catch block and re-throw the exception with the call stack preserved. While for the second catch block, we create another exception using the throw keyword. For this we use a new instance of  ApplicationException that derives from System.Exception.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

Nested Try-Catch Block

A nested try-catch block is a try-catch in another try-catch block. It is important to note that while using nested try-catch blocks, the first suitable catch block that follows the try block will handle any raised exception:

try
{
    var result = 100 / divider;
    try
    {
        var array = new int[0];
        result = array[result];

    }
    catch (IndexOutOfRangeException ex)
    {
        Console.WriteLine("{0} First exception caught.", ex.Message);
        throw;
    }
}
catch (DivideByZeroException e)
{
    Console.WriteLine("Outer catch", e.Message);
    throw;
}

This code sample will raise a DivideByZeroException exception if divider is set as zero. This occurs because the inner catch cannot handle the raised exception. On the other hand if the divider is greater than zero IndexOutOfRangeException  will be raised. 

Invalid Catch Block

When we have a try block followed by the more than one general catch block, we call that catch block an Invalid Catch Block:

try
{
    int[] a = new int[5];
    a[10] = 12;
}
catch
{
    Console.WriteLine("{0} Exception caught.");
}
catch (Exception ex) 
{ 
    Console.WriteLine("{0} Exception caught.",ex.Message); 
}

The last catch block will be flagged as the CS1017 compiler error. This is because both are general catch of a try block

Furthermore, to make it valid, a general catch block must be the last one:

try
{
    var result = 3000 / divider;
    Math.Sign(double.NaN);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Inner catch", ex.Message);
}
catch
{
    Console.WriteLine("Outer catch");
}

Another way to add a general catch is by creating a catch block with an Exception parameter:

try
{
    var result = 3000 / divider;
    Math.Sign(double.NaN);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Inner catch", ex.Message);
    throw;
}
catch (Exception ex)
{
    Console.WriteLine("Outer catch", ex.Message);
    throw;
}

Exception Filters

The exceptions filter feature makes it possible to add a condition to a catch block. By applying a filter to a catch block, the code in the block is implemented if the condition is true. To create a filtered catch block, we have to append the when keyword with the required condition:

try
{
    return words[index];
}
catch (IndexOutOfRangeException e) when (index < 0)
{
    throw new IndexOutOfRangeException(
        "index of an array cannot be negative.");
}
catch (IndexOutOfRangeException e)
{
    throw new IndexOutOfRangeException(
        "index cannot be greater than the array size.");
}

When the value of index is less than zero, IndexOutOfRangeException is raised and the control of execution flows to the first catch block. Similarly, if the value of the index is greater than the length of the words array IndexOutOfRangeException  is also raised but the control of execution flows to the second catch block as a result of the filter added to the first catch block.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

Conclusion

In this article, we’ve learned about the try-catch block in C#, and when to use it. We’ve also answered some questions on its implementation in our code.