As much as we’d like to write perfect programs, there’s going to be a point where you need to think about handling error cases in your C# applications. That’s why you’re here: for an introduction to try catch in C#!
In this article, I’ll explain the very basics of how to structure try catch blocks and what each block is responsible for. The code is simple and aims to help explain some of the basic concepts to beginners working in C#. If that sounds like you, read on!
Overview of Exception Handling in CSharp
Let’s face it: there are going to be exceptions in our C# applications and services at some point. We hate to admit it, but it’s a reality that we have to understand how to work with.
Exception handling in C# is done by using what’s called a try/catch block. So try catch in C# is how we set up code to have some type of protection and define the logic for how we want to deal with those exceptions.
The following sections will explain the two main pieces, try and catch, and later on we’ll see an extra third component we can add to the try catch blocks we create in C#. You can follow along with this video on how to handle exceptions with try/catch blocks in C# as well:
The Try Block
The try block is the first important part of our exception handling that we need to look at. It serves the purpose of enclosing the code that might potentially throw an exception. By placing the code within a try block, we ensure that any exceptions that occur can be caught and handled appropriately — but it’s not handled by this block, this block just tells us what will get handled.
The try block is structured using the keyword “try”, followed by a set of curly braces that contain the code we want to monitor for exceptions. This code is often referred to as the “protected code”, as it is within this block that we anticipate will be potentially throwing exceptions. Here’s an example of a try block in C#:
try
{
// Code that might throw an exception
int result = Divide(10, 0);
Console.WriteLine("The result is: " + result);
}
In this example, we have a function called “Divide” that takes two integers as parameters and attempts to perform a division operation. However, if the second parameter is zero, it will throw a divide by zero exception. By enclosing this code within a try block, we can catch and handle this exception appropriately — but we need another block to actually handle it! And that’s where we’re headed in the next section!
The Catch Block
The main purpose of the catch block is to provide a mechanism for handling exceptions gracefully. The try block allows us to define what we want to protect, and the catch block allows us to define what we’ll handle and how we’ll handle it.
When an exception is thrown, the catch block allows us to specify the actions to be taken in response to that exception. These actions could include logging the exception, displaying an error message to the user, or taking any other necessary corrective measures. The choice is yours!
To catch a specific type of exception, we use the catch keyword followed by the exception type we want to handle. This allows us to handle different exceptions in different ways, tailoring our response based on the specific type of exception.
Here’s an example of a catch block that handles a specific exception type, in this case, the DivideByZeroException
:
try
{
int result = 10 / 0; // This will throw a DivideByZeroException
}
catch (DivideByZeroException ex)
{
Console.WriteLine("An error occurred: " + ex.Message);
}
In this example, the code within the try block attempts to divide the number 10 by 0, which will result in a DivideByZeroException
. The catch block catches this exception and executes the code within its block, which in this case, simply writes an error message to the console, displaying the message provided by the exception.
You can also use a base exception class or omit the exception portion altogether to catch all (well, almost all) exceptions:
try
{
}
catch (Exception ex)
{
// this will catch all catchable exceptions
}
// OR
try
{
}
catch
{
// note that we don't have an exception instance in this case!
}
Handling Multiple Exceptions
If one exception can be thrown from a called piece of code, odds are that many can. As a result, it’s common to encounter scenarios where multiple types of exceptions can occur within a single try block. These exceptions may arise due to different errors or exceptional situations that need to be handled appropriately. In this section, I’ll discuss how to handle multiple exceptions by using either multiple catch blocks or a single catch block with multiple exception types. Let’s see how!
Multiple Catch Blocks Approach
When handling multiple exceptions, one approach is to use multiple catch blocks. Each catch block is responsible for handling a specific type of exception. By using this approach, you can specify separate code blocks to handle different exceptions based on their respective types.
Here is an example that demonstrates the multiple catch blocks approach:
try
{
// Code that may throw exceptions
}
catch (ArgumentNullException ex)
{
// Handle ArgumentNullException
}
catch (DivideByZeroException ex)
{
// Handle DivideByZeroException
}
catch (FormatException ex)
{
// Handle FormatException
}
In the above example, the catch block following the try block will catch and handle any ArgumentNullException
that occurs. Similarly, the catch blocks for DivideByZeroException
and FormatException
will handle their respective exceptions.
Single Catch Block with When Filtering
Another approach for handling multiple exceptions is to use a single catch block that can handle multiple exception types. This approach can be useful when you want to handle multiple exceptions in the same way without writing separate catch blocks for each one.
Here is an example that demonstrates the single catch block approach:
try
{
// Code that may throw exceptions
}
catch (Exception ex) when // look at this fancy keyword!
(
ex is ArgumentNullException ||
ex is DivideByZeroException ||
ex is FormatException
)
{
// Handle ArgumentNullException, DivideByZeroException, and FormatException
}
In the above example, the catch block will handle any ArgumentNullException
, DivideByZeroException
, or FormatException
that occurs within the try block. This is because we’ve placed a filter onto the exception handler using the when
keyword!
I should note that you can do more filtering than just type-checking with the when keyword. You’re able to examine the different properties for the exception instance and create filters accordingly. If you needed to filter by a certain exception message or error code on a particular exception type, you can do that with the when
keyword.
The Finally Block
The finally block in C# is used to define a section of code that will always be executed, regardless of whether an exception occurred or not. Its purpose is to ensure that certain tasks, such as releasing resources or performing cleanup operations, are always carried out, regardless of the outcome of the try block.
The finally block is executed after the try and catch blocks, whether an exception is thrown or not. It’s written with the keyword “finally” followed by a set of curly braces {}. Any code within these braces will be executed immediately after the catch block finishes.
Finally Block Use Case
One common use case for the finally block is to release any resources that were acquired within the try block. This could include closing file handles, database connections, network sockets, or any other resource that needs to be freed up. By placing the cleanup code in the finally block, you can ensure that it will be executed even if an exception is thrown in the try block.
Here is an example that demonstrates the use of the finally block for resource cleanup:
try
{
// Code that could potentially throw an exception
// Acquiring resources
// Performing operations
}
catch(Exception ex)
{
// Exception handling code
// Logging or displaying error messages
}
finally
{
// Code to release resources
// Closing file handles, database connections, etc.
}
In this example, if an exception is thrown in the try block, the program flow will jump to the catch block to handle the exception. However, regardless of whether an exception is thrown or not, the code within the finally block will always be executed afterwards. This ensures that any acquired resources are properly released, preventing resource leaks and maintaining the stability of the program.
Alternative to Finally Blocks: IDisposable
The finally block that we can use when it comes to try catch in C# is very helpful — but try/catch syntax is already a bit clunky to begin with. It’s a common pattern that we want to clean up resources and if we’re being defensive, we want to make sure that we clean them up even when we have error scenarios.
We can use the IDisposable
interface and a using block to help support this. So instead of requiring a finally block to clean things up, you could have code inside of a Dispose()
method on the object that implements IDisposable
. The code would be simplified to this:
public class MyClass : IDisposable
{
public void Dispose()
{
// TODO: do your cleanup... same
// stuff that you might have wanted
// in your finally block!
}
}
using (Myclass myClass = new())
{
// do stuff with myClass
}
// dispose runs after we leave the block
This is a lot less clunky, in my opinion than a whole try/finally block to support cleanup — but of course, this will not catch errors just ensure you can still run cleanup code. We can simplify this further with implicit using now where the Dispose()
method runs when the variable goes out of scope:
public void SomeMethod()
{
// implicit using...
using Myclass myClass = new();
// do stuff with myClass
// dispose runs at the end here since the variable goes out of scope
}
Considerations for Try Catch in CSharp
Now that you’ve seen several different ways we can use try catch in C# as well as some different use cases, let’s talk about some considerations. We’ve seen the tools we have to use, but some guidance on using them appropriately seems fitting:
Is it REALLY exceptional?: Consider if the exception you’re catching is truly exceptional. Unfortunately, many libraries and programs have exceptions thrown that are not truly exceptional behavior for an application. This can complicate logic and slow things down. So if you don’t need to throw them, don’t. But you also might need to catch them because you don’t want the entire program to terminate.
Catch All vs Catch Specific: Many people frown upon using a try/catch block that attempts to catch all exceptions. The argument is that you could be masking an underlying problem with a blanket handler. However, there are absolutely situations (many of which I’ve used in a professional setting) where we’ve been better off with a catch-all. Some things we are calling are outside of our control with respect to the errors they throw, and if you don’t need your entire application or service to end… then you need to handle them.
Proper Cleanup: Consider if you need something like a finally block or to use the dispose pattern. Remember, using the dispose pattern can help clean up the code but alone it does not prevent exceptions from being thrown.
Logging & Telemetry: You’ll not only want to understand recovery patterns when using try catch in C#, but you should consider if you need to be doing something with this error information. If you have a live service running in production, there are many cases where you’ll want to know what’s going wrong or what can be improved. Logging and telemetry can help!
Wrapping Up Try Catch in CSharp
Now you’ve had a chance to see the basics of try catch in C#! I’ve aimed to help familiarize you with the purpose of the different blocks, and introduced the finally block as well! With some basic code examples to refer to along with some extra considerations to keep in mind when programming, you’re all set to start catching exceptions in your code!
If you found this useful and you’re looking for more learning opportunities, consider subscribing to my free weekly software engineering newsletter and check out my free videos on YouTube! Meet other like-minded software engineers and join my Discord community!
Want More Dev Leader Content?
- Follow along on this platform if you haven’t already!
- Subscribe to my free weekly software engineering and dotnet-focused newsletter. I include exclusive articles and early access to videos: SUBSCRIBE FOR FREE
- Looking for courses? Check out my offerings: VIEW COURSES
- E-Books & other resources: VIEW RESOURCES
- Watch hundreds of full-length videos on my YouTube channel: VISIT CHANNEL
- Visit my website for hundreds of articles on various software engineering topics (including code snippets): VISIT WEBSITE
- Check out the repository with many code examples from my articles and videos on GitHub: VIEW REPOSITORY
Top comments (1)
Some fun moments:
OutOfMemoryException
, but that can be raised in any block of code, that does not show a real source of high memory usageStackOverflowException
(but you could in the past, but I would raise it anyway)