Exception Handling In C#

by Kenji Elzerman
Exception Handling In C# - Kens Learning Curve

While working on your application you might run into an exception, which can be frustrating. An exception usually means that the application can’t run any further and it crashes. Luckily for us, we can find and handle those exceptions while developing the application. This way we can ensure the application doesn’t crash, but it shows the user a nice error and lets the user fix it. But in some cases, the user can’t help it and we notify the user this is it, and the application will stop… The user should contact support. In this article, I want to show you what exception handling in C# is and how we apply it.

Prefer to see the video?

Exception handling in C# in short

Before I am going to dive into the code, I want to tell you a bit about exceptions. An exception in C# is an error or unexpected situation that occurs while your code is running, a.k.a. you are running the application. When an exception is thrown, it means that something has gone wrong and the code cannot continue executing as expected.

If you don’t do anything the application will stop working and crash, showing messages a user won’t understand or doesn’t need to see. This is something we want to avoid at all costs. We need to handle those exceptions so the user gets a nice message and maybe log the errors for the developer to see what went wrong.
And this is what we call exception handling in C#: handling the exception.

Thrown

Exceptions are thrown, which describes the act of sending (or throwing) an error to another part of the code. When your application throws an exception it signals that something has gone wrong. The flow of the execution is disrupted and it needs to be handled if the flow wants to continue.
To continue the flow you need to catch the exception and handle it accordingly. You can write code so the application can recover from the error and continue. If the exception isn’t caught, the application will crash or do something you don’t want it to do.

When exception handling in C# you catch the thrown exceptions, examine them, and act accordingly. Or you ignore the exception and let the application crash.

Different types

An exception is a type, but there are many types of exceptions. When exception handling in C# you need to know there are different types of exceptions, but it is impossible to learn them all. There are exceptions for files (file not found, file already exists, directory not found, etc.), for variables (value cannot be null, incorrect value, etc.), and many more. Most exceptions will be thrown by C# and .NET, so it’s good to know you need to read the exceptions and sometimes look them up on the Internet. But you can also throw exceptions on your own when needed. If you are doing validations for example.

Okay, enough theory. Let’s dive into exceptions!

Simple exception

I have created a very simple piece of C# code:

Console.Write("First number: ");
int firstNumber = int.Parse(Console.ReadLine());

Console.Write("Second number: ");
int secondNumber = int.Parse(Console.ReadLine());

Console.WriteLine($"The result is: {firstNumber} / {secondNumber} = {firstNumber /  secondNumber}");

For the mathematicians here: Do you see what could go wrong here? Knowing what would go wrong in your code is the first step of exception handling in C#. But it’s not always possible, since you could be using 3rd party components or sources, like a database or user input.

An exception

Let’s run the application and enter the values 6 and 2. The outcome is 3, because 6 / 2 = 3. Awesome. Now restart the app and enter 6 and 0:

attempted to divide by zero - Exception Handling In C# - Kens Learning Curve

Boom! Exception! It’s mathematically impossible to divide a number by zero, thus you get an exception. The code has no idea how to solve this, so it throws an exception and ends the application.

Let’s look at the exception itself: The first thing I notice is the “Exception Unhandled”, which is correct. If the exception was handled it wouldn’t show this message.
The second part is “System.DividedByZeroException”. This is the exception type and it already gives us hints about what is wrong here. Some exceptions come with an extra message, like this one: ‘Attempted to divide by zero’. Okay, it does say the same as the type, but still… It’s a message.
Then you have some extra options, like asking Copilot, reviewing the call stack, and more. I am not going into details on these.

If you continue the application, the console will look like this:

attempted to divide by zero in console app - Exception Handling In C# - Kens Learning Curve.png

Doesn’t look really user-friendly.

Handle the exception

We don’t want our application to crash and instead of letting that happen, we should handle the exception and show the user a friendly message. Exception handling in C# is pretty easy: we need to use the try-catch. The try-catch consists of two blocks. One block executes the code and one block that catches the exception. This is the first step of exception handling in C#. And here is how it looks in C#:

Console.Write("First number: ");
int firstNumber = int.Parse(Console.ReadLine());

Console.Write("Second number: ");
int secondNumber = int.Parse(Console.ReadLine());

try
{
    Console.WriteLine($"The result is: {firstNumber} / {secondNumber} = {firstNumber /  secondNumber}");
}
catch (Exception ex)
{
    Console.WriteLine($"Something went wrong: {ex.Message}");
}

I have isolated the firstNumber divided by secondNumber. This way, if something happens and causes an exception, it is caught by the catch. In short: If the application reaches an exception inside the try, the catch will … Catch it.

The catch has a parameter: Exception ex. Exception is the base of all exceptions. Earlier, we got a DividedByZeroException. If you look it up and look at the inheritance, you will eventually find the Exception class. So, it doesn’t matter which exception type is thrown, the Exception will always be hit in this catch.

The Exception has a few properties and methods. One of them is Message, which is the message an exception could throw. Other well-known properties and methods are the StackTrace and Source. An exception could also have an exception, which is called the InnerException. The InnerException is used to give more information about the first exception, but it’s not always there.

Exception handled in console app - Exception Handling In C# - Kens Learning Curve.png

This is a very basic example of exception handling in C#. The application still stops, but it doesn’t crash. It also gives a nice message to the user about what went wrong. In this case, the user could try again and avoid the problem.

A better exception handling in C#

While the previous example works and shows how you could do it, there is a better way. Yes, you could check if the input of the user is correct and act on it instead of throwing exceptions, but this is an article about exceptions. What I am pointing at is the catch(Exception ex). We now know that all exception types are derived by the type Exception, so this catch does work. But it doesn’t give us any clue about what is from. Especially when the exception doesn’t give a message. In such cases “something” went wrong.
With exception handling in C#, it is better to handle the known exception types rather than the global Exception type. This way you know what exception is thrown and maybe you can even fix it instead of showing a message to the user.

Let’s continue with the previous example. We get a DividedByZeroException, this we know. We can add another catch, especially for this exception type, and be more clear to the user:

Console.Write("First number: ");
int firstNumber = int.Parse(Console.ReadLine());

Console.Write("Second number: ");
int secondNumber = int.Parse(Console.ReadLine());

try
{
    Console.WriteLine($"The result is: {firstNumber} / {secondNumber} = {firstNumber /  secondNumber}");
}
catch (DivideByZeroException dividedByZero)
{
    Console.WriteLine($"You can't divide {firstNumber} by 0!");
}
catch (Exception ex)
{
    Console.WriteLine($"Something went wrong: {ex.Message}");
}

When the exception type DividedByZeroException is thrown, the catch with that exception is executed, not the Exception type. Because we then know what is happening, we can write a correct message.

You can add more types to the catch-clauses, but at some point, you need to stop. It has no point in writing 20 different catches. Only use those that are relevant and that need to be handled.
I would still keep the catch with the Exception type. If something else is going wrong you still get the nice error for the user and you know where to start looking.

Life after the exception

Do remember that the code after the try-catch will still be executed. For example this code:

Console.Write("First number: ");
int firstNumber = int.Parse(Console.ReadLine());

Console.Write("Second number: ");
int secondNumber = int.Parse(Console.ReadLine());

int? result = null;

try
{
    result = firstNumber /  secondNumber;
}
catch (DivideByZeroException dividedByZero)
{
    Console.WriteLine($"You can't divide {firstNumber} by 0!");
}
catch (Exception ex)
{
    Console.WriteLine($"Something went wrong: {ex.Message}");
}

Console.WriteLine($"The result of {firstNumber} / {secondNumber} is {result}");

If the firstNumber is 6 and the secondNumber is 0, the exception is thrown and handled. But the last line will still be executed and gives a strange result:

Code execution after try-catch - Exception Handling In C# - Kens Learning Curve.png

Keep this in mind!

Throwing exceptions on your own

The past examples are handling exceptions you didn’t know were coming. But sometimes you know a certain piece of code will throw an exception or you need to make sure the user knows the code will crash if nothing is being done about it.

In such cases, you can throw exceptions on your own. We usually do this when validating data in the back end of the application. When a value from a user is incorrect we can throw an exception and the front-end will handle it, making sure the user gets a nice message and the opportunity to fix it (if possible).

Let’s create a method of our example code:

public class Calculations
{
    public int Divide(int firstNumber, int secondNumber)
    {
        return firstNumber /  secondNumber;
    }
}

Let’s say this is our front-end console application:

Main();

static void Main()
{
    Console.Write("First number: ");
    int firstNumber = int.Parse(Console.ReadLine());
    Console.Write("Second number: ");
    int secondNumber = int.Parse(Console.ReadLine());

    Calculations cal = new Calculations();
    int result = cal.Divide(firstNumber, secondNumber);

    Console.WriteLine($"The result of {firstNumber} / {secondNumber} is {result}");
}

We haven’t applied exception handling in C#, so we need to add it. But I want to keep the DividedByZeroException from happening. Let’s assume this class is in a C# library somewhere and it’s used by other applications as well. It has no point in handling the DividedByZeroException, since each application might handle it differently.

I want to check if the secondNumber in the Divide function is not zero. I would add the following validation:

public class Calculations
{
    public int Divide(int firstNumber, int secondNumber)
    {
        if (secondNumber == 0)
        {
            throw new ArgumentException("Second number can't be zero!");
        }

        return firstNumber /  secondNumber;
    }
}

I throw a different exception, the ArgumentException, which indicates an argument of the method is incorrect. I can handle this exception in the front-end application:

static void Main()
{
    Console.Write("First number: ");
    int firstNumber = int.Parse(Console.ReadLine());
    Console.Write("Second number: ");
    int secondNumber = int.Parse(Console.ReadLine());

    Calculations cal = new Calculations();
    try
    {
        int result = cal.Divide(firstNumber, secondNumber);

        Console.WriteLine($"The result of {firstNumber} / {secondNumber} is {result}");
    }
    catch (ArgumentException argumentException)
    {
        Console.Clear();
        Console.WriteLine(argumentException.Message);
        Console.WriteLine();
        Main();
    }
    catch (Exception exception)
    {
        Console.WriteLine("I don't know what you did, but this isn't working!");
        throw;
    }
}

I put the cal.Divide and the Console.WriteLine in the try; it has no point in executing the Console.WriteLine if the result can’t be calculated. Then I have the first catch, which catches the ArgumentException. I clear the screen, write the message, write an empty line, and execute the Main() again. This way I show the user what is wrong and allow trying again. A nice way of handling (and avoiding) an exception.

Throw;

When there is some different exception thrown, the Exception catch is hit. It will write down that it can’t be handled and rethrows the exception. The user still gets a nice message, but the debugger will also be hit. Another reason to rethrow the exception is to get the stack trace. When you handle an exception you might lose the path it has taken to get to the exception.

The downside of rethrowing is that the user gets the nice message, but also the exception, as shown earlier in this article. Use it wisely!

Debugging an exception

It’s good to apply exception handling in C#, but it’s even better to debug them. If you don’t know why an exception is thrown it’s more reason to debug it.

Let’s use the following code:

string filePath = @"my_files\\file.txt";
string directoryPath = Path.GetDirectoryName(filePath);

FileStream fileStream = new(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
StreamWriter writer = new(fileStream);

writer.WriteLine("Some text for the file");

Call Stack

If you run this you will get an exception:

Directory not found - Exception Handling In C# - Kens Learning Curve

Let’s just assume we have no idea why and how. Of course, we could start by reading the message, but that’s to easy. If you have a huge project, the message could not give a good idea of where the error occurs. Instead, click the “Show Call Stack”. This will open the call stack (duh) and show you the path of where the exception occurs:

Call stack after exception - Exception Handling In C# - Kens Learning Curve

The exception was thrown in the Program.<Main>, line 4. Which is where the FileStream is created.

Fix it without message

To handle this exception I have to change the code from the console application to this:

Start();

static void Start()
{
    string filePath = @"my_files\\file.txt";
    string directoryPath = Path.GetDirectoryName(filePath);

    try
    {
        FileStream fileStream = new(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
        StreamWriter writer = new(fileStream);

        writer.WriteLine("Some text for the file");
    }
    catch (DirectoryNotFoundException directoryNotFound)
    {
        Directory.CreateDirectory(directoryPath);
        Start();
    }
    catch (Exception ex)
    {
        throw;
    }
}

I’ve added the try-catch and handled the DirectoryNotFoundException. If this occurs I create the directory and try again. When a different kind of exception is thrown, the Exception catch is executed.

Implementing the finally

A FileStream and StreamWriter have to be closed after they are opened. In our example, the streams are opened, but not closed. Only when the application stops. If the file stream is opened and the writer gives an error, the file stream is not closed. If this were an active API this would cause other errors, if the file was used in other locations of the application.

To fix this we can implement the finally. We can use this clause of the try-catch to close the streams if they are loaded. One of the biggest advantages of the finally is that the code block inside this clause is always executed, exception or not.

Look at the following code:

Start();

static void Start()
{
    string filePath = @"my_files\\file.txt";
    string directoryPath = Path.GetDirectoryName(filePath);

    FileStream? fileStream = null;
    StreamWriter? writer = null;

    try
    {
        fileStream = new(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
        writer = new(fileStream);

        writer.WriteLine("Some text for the file");
    }
    catch (DirectoryNotFoundException directoryNotFound)
    {
        Directory.CreateDirectory(directoryPath);
        Start();
    }
    catch (Exception ex)
    {
        throw;
    }
    finally
    {
        fileStream?.Close();
        writer?.Close();
    }
}

I have moved the variable declarations of the fileStream and writer outside the try-catch. This way, I can use them later on. Then the try will execute the code. If all goes well, with no exceptions, the finally will be called. It will check if the fileStream and writer are not null. If they are not null the fileStream and/or the writer will be closed.
If there was an exception, let’s the content of the text that is written to the writer is incorrect, the catch Exception is executed. After this clause is done, the finally is executed nevertheless, closing the fileStream and/or the writer.

Exception filtering

In some cases, the same exception type is thrown for different reasons. These reasons are usually hidden in the message. You don’t want an enormous if-statement in your catch-clause. What you do want to do is use exception filtering.

The following code could be a realistic piece of code for your application.

try
{
    int result = calc.DoCalculations(firstNumber, secondNumber);
}
catch (Exception ex)
{
    if (ex.Message == "Can't divide by zero")
    {

    }
    else if (ex.Message == "Unable to use negatives")
    {

    }
    else if (ex.Message == "Strings are not allowed")
    {

    }
}

We see an if-statement inside the catch-clause. Although this isn’t wrong, it does make it look bulky and it’s harder to maintain. Instead of using an if-statement, let’s implement exception filtering:

try
{
    int result = calc.DoCalculations(firstNumber, secondNumber);
}
catch (Exception ex) when (ex.Message == "Can't divide by zero")
{

}
catch (Exception ex) when (ex.Message == "Unable to use negatives")
{

}
catch (Exception ex) when (ex.Message == "Strings are not allowed")
{

}

The when keyword says: Execute the code of this catch clause when the message of ex equals… Text. Some kind of decision making in C#. Using exception filtering makes your code less bulky and easier to control.

Conclusion on exception handling in C#

Exception handling is a vital part of your job as a developer. Exceptions are here to tell us something is really wrong and it should be handled. Users don’t want to see exceptions since the messages are hard to understand and even scary sometimes. Instead, we should handle the exceptions and give the users a nice, friendly message that the application has crashed.

Although I use exceptions a lot, handling and throwing them, I think most of the work can be done by using if statements. The if statements are a bit more friendly and easier to control. Exceptions do take more memory from the device and could cause other problems.

Do know how to use the try-catch, with or without the finally, and how to handle the different types of exceptions.

Relates Articles

About

Kens Learning Curve is all about learning C# and all that comes along with it. From short, practical tutorials to learning from A to Z.

 

All subjects are tried and used by myself and I don’t use ChatGPT to write the texts.

Contact

Use the contact form to contact me or send me an e-mail.

The contact form can be found here.

Email: info@kenslearningcurve.com

@2023 – All Right Reserved. Designed and Developed by Kens Learning Curve

Table Of Contents
Kens Learning Curve