When does the try, catch and finally code blocks run in C#?

Published: Monday 17 June 2024

The try, catch and finally statements are excellent for catching unhandled exceptions. They are also good for ensuring that a code block always runs regardless of whether an exception is thrown or not.

But do they always run? Let's investigate!

The purpose of the try block

The purpose of the try block is to write code where there is a risk of an exception being thrown.

There are many scenarios why you would want to do this. One example is communicating with a service outside of the application, such as a database or an API. These are prone to unexpected issues, such as connectivity or resource management issues.

The catch statement ensures that these exceptions can be handled without the application crashing.

In this example, we are making a call to an API and logging the exception to the console if one occurs.

var httpClient = new HttpClient();

try
{
    var response = await httpClient.GetAsync("https://www.roundthecode.com/api/products/1");

    response.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

Exception classes

In the code snippet above, we were catching the exception if it uses the Exception class. As all exceptions inherit this class, it will always catch any exception thrown.

But C# has a number of other common exceptions that you can use. They include:

  • ArgumentException - One of the arguments provided to a method is not valid
  • DivideByZeroException - Attempting to divide a number by zero
  • IndexOutOfRangeException - Attempting to get the value of an array index that is outside the bounds of the array
  • NullReferenceException - Using a null reference object in a way that requires an instance, such as calling a property or a method
  • OutOfMemoryException - The application can not allocated enough memory

Any of these exceptions can be handled in a separate catch code block.

In this example, we are passing in an object as a parameter into the method and catching the exception if a NullReferenceException is thrown:

public class CategoryService
{
	public string? GetCategoryName(Category category)
	{
		try
		{
			return category.Name;
		}
		catch (NullReferenceException)
		{
			return null;
		}
	}
}

However, if any other exceptions are thrown, they would be unhandled.

But you can add multiple catch code blocks to a try statement. In this example, if the exception thrown is a NullReferenceException, the method will return null. If any other exceptions are thrown, the exception is written to the console and then the method returns null.

public class CategoryService
{
	public string? GetCategoryName(Category category)
	{
		try
		{
			return category.Name;
		}
		catch (NullReferenceException)
		{
			// Code block will only be run if a NullReferenceException is thrown in the 'try' code block
			return null;
		}
		catch (Exception ex)
		{
			// Code block will run if anything except a NullReferenceException is thrown in the 'try' code block
			Console.WriteLine(ex.Message);
			return null;
		}
	}
}

How to write your own Exception class

There may be instances when you want to write your own exception class. This can be done by creating a class and inheriting the Exception class.

One of the benefits of creating your own is that you can add your own members that are specific to the exception.

However, you'll need to override some of the base constructors to add an error message and an inner exception.

In this example, we've created a CategoryException class. We've included three of the base classes with constructors that:

  • Are parameterless
  • Has a message as a string as a parameter
  • Has a message as a string and an inner exception as parameters

In-addition, we are passing in the category name and it's being saved as a read-only property as part of the exception.

public class CategoryException : Exception
{
	public string CategoryName { get; }

	public CategoryException(string categoryName) 
		: base()
	{
		// Does not add an error message
		CategoryName = categoryName;
	}

	public CategoryException(string categoryName, string message) 
		: base(message)
	{
		// Add an error message through the base 'Exception' class
		CategoryName = categoryName;
	}

	public CategoryException(string categoryName, string message, Exception? innerException) 
		: base(message, innerException)
	{
		// Add an error message and inner exception through the base 'Exception' class
		CategoryName = categoryName;
	}
}

If we wish to catch this exception, we can add it to our try, catch statement.

public class CategoryService
{
	public string? GetCategoryName(Category category)
	{
		try
		{
			return category.Name;
		}
		catch (CategoryException)
		{
			return null;
		}
		catch (NullReferenceException)
		{
			return null;
		}
		catch (Exception ex)
		{
			Console.WriteLine(ex.Message);
			return null;
		}
	}
}

When do the code blocks run?

Now that we've established how the try, catch statement works, that's examine when they run and how the finally statement works.

No exception in try block

var load = true;

try
{
	var a = 9 + 9;
}
catch (Exception ex)
{
	// Does not run as there is no exception
}
finally
{
	// Runs after the try code block
	load = false;
}

There's a try code block that doesn't throw an exception. We are catching any exception, but not doing anything with it.

in this situation, the try block will execute, but as no exception is thrown, it will ignore the catch block and go straight to the finally block.

Exception is thrown in the try block

var load = true;

try
{
	var a = int.Parse("not a number"); // FormatException thrown
	
	var b = 9 + 9; // This does not run as an exception has already been thrown
}
catch (Exception ex)
{
	Console.WriteLine(ex.Message); // Will write the FormatException to the Console
}
finally
{
	// Runs after the catch code block
	load = false;
}

In this situation, the try block will run up until the point there is an exception.

As "not a number" is not a valid integer, it will throw a FormatException. As the exception is thrown before the b variable is declared, that code snippet will not run.

The finally block will run after the catch block has been executed.

Exception is thrown but not the one being catched

var load = true;

try
{
	var a = int.Parse("not a number"); // FormatException thrown

	var b = 9 + 9; // This does not run as an exception has already been thrown
}
catch (NullReferenceException ex)
{
	// This code block will not run as it's a FormatException being thrown
	Console.WriteLine(ex.Message);
}
finally
{
	// Runs after the try code block in an ASP.NET Core app, but not a Console application
	load = false;
}

The try block is throwing the same FormatException.

However, we are only catching any exceptions that are a NullReferenceException type. So the catch block won't run and the application will throw an exception instead.

Before it does that, it will execute the finally block if it's an ASP.NET Core app. If it's a console application, it stops before it has a chance to run the finally block.

Exception is thrown in the catch block

var load = true;

try
{
	var a = int.Parse("not a number"); // FormatException thrown
 
	var b = 9 + 9; // This does not run as an exception has already been thrown
}
catch (Exception ex)
{
	// Throws the exception causing the application to throw an exception
	throw ex; 
}
finally
{
	// Runs after the catch code block in an ASP.NET Core, but not in a Console application
	load = false;
}

When the try block throws the FormatException, it will execute the catch block.

But because the exception in the catch block is also being thrown, the application will throw an exception.

The finally block will run before the application throws an exception in an ASP.NET Core app. But in a Console application, it will not run as the application would have already stopped.

No catch block

var load = true;

try
{
	var a = int.Parse("not a number"); // FormatException thrown

	var b = 9 + 9; // This does not run as an exception has already been thrown
}
finally
{
	// Runs after the try code block in an ASP.NET Core application, but not a Console application
	load = false;
}

We have no catch statement and the FormatException is being thrown in the try statement.

Like with the other examples, the application will throw an exception as it's being unhandled. 

But even without the catch block, the finally block will still run in an ASP.NET Core app. But like with the other scenarios, the Console application will stop before it can run the finally block.

Exception thrown in the finally block

var load = true;

try
{
	var a = int.Parse("not a number"); // FormatException thrown

	var b = 9 + 9; // This does not run as an exception has already been thrown
}
catch (Exception ex)
{
	Console.WriteLine(ex.Message); // Will write the FormatException message to the Console
}
finally
{
	var c = int.Parse("not a number"); // FormatException thrown

	load = false; // This does not run as an exception has already been thrown
}

The FormatException gets thrown in the try block.

The exception is captured and logged to the console.

The finally block will still run. However, a FormatException is thrown when we try to pass the integer with a non-numeric string.

Therefore, an exception will be thrown and the load boolean will not be changed to false.

Watch the video

Watch our video where we go through the try, catch and finally code blocks, what exceptions are available, how to write your own custom exception and when the code blocks run.

Catch runs if there is an exception. Finally always runs

In conclusion, the catch statement will only run if an exception is thrown in the try block, and it's the same exception declared in the catch statement.

In ASP.NET Core, the finally statement will always run, regardless of whether an exception is thrown in a try or catch block, or whether that exception is handled or not.

But with a Console application, the finally statement doesn't run if an unhandled exception occurs.

When an unhandled exception is thrown, it will not execute any code in that block that proceeds the thrown exception. This applies to any of the code blocks.