When Things Go Wrong - Exception Handling

There will be occasions when you are testing your code, where the application does not work the way you intended. Sometimes it will crash with an error that you weren’t expecting but could have avoided. Sometimes it doesn’t crash but provides the wrong data or displays the wrong message. Having the ability to troubleshoot application issues and prevent avoidable application crashes is vital for a skilled Software Developer.


Exception Handling

There will be times in your application where wrong user input has the potential of causing the application to crash. These problems in the execution of your program are called “exceptions.” When these errors occur, the application will stop its execution and provide a generated error message. This error process is often verbalized as “the application throwing an exception.” These exceptions contain valuable information about the error.


Exception Information


Message

The Message property of the exception provides the description of the exception and usually the reason the exception occurred. These messages are usually targeted to a software developer who can understand the information and take steps to resolve or avoid the exception.

 static void Main(string[] args)
 {
 int test = int.Parse("DD");
 } 

When this line of code executes, it will throw a FormatException.

The message of that exception is “Input string was not in a correct format.”


StackTrace

To understand what StackTrace does, you need to know about the “call stack.” Once your program starts execution, a list of all the methods called is being populated. This list is being tracked by the system so that any point it knows what path application flow has to travel to exit the program cleanly.


Your “main” method calls other methods, which then, in turn, call other methods. The application needs to keep track of the path back to the main method, so it stores that path as a stack of methods.


The StackTrace property will return the stack of method calls that led up to the issue that threw the exception.


To demonstrate this concept, I will bury the offending code statement down a couple of method calls.

 static void Main(string[] args)
 {
 GetUserInput();
 }
 
 private static void GetUserInput()
 {
 GetUserAge();
 }
 private static void GetUserAge()
 {
 int test = int.Parse("DD");
 }

When this code is executed, a FormatException is still thrown. However, the StackTrace provides a little more information


The value of the StackTrace is:

at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type)

at System.Number.ParseInt32(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info)

at System.Int32.Parse(String s)

at ConsoleApp2.Program.GetUserAge() in C:\Users\Trema\source\repos\ConsoleApp2\ConsoleApp2\Program.cs:line 25

at ConsoleApp2.Program.GetUserInput() in C:\Users\Trema\source\repos\ConsoleApp2\ConsoleApp2\Program.cs:line 21

at ConsoleApp2.Program.Main(String[] args) in C:\Users\Trema\source\repos\ConsoleApp2\ConsoleApp2\Program.cs:line 16

It’s easiest to read the call stack from the bottom up. Reading this call stack, we can see that the Main method called the GetUserInput method, which called the GetUserAge method. We then see that GetUserAge called the Parse method, which called the ParseInt32 method. Since the actual error occurred in the ParseInt32 method, that method called the ThrowOverflowOrFormatException method, which generated the exception.


InnerException

There will be times during application execution when a chain of exception occurs. For example, if your application tries to parse the string “scott” into an integer variable, a FormatException gets thrown. If you have set up your application to log exceptions into a file but that file can not be found a second exception, FileNotFound, is thrown. In this scenario, the exception message will give detail regarding the FileNotFound exception but will not have any information regarding the original FormatException. Luckily that information is stored in the InnerException property. You can use InnerException to drill down into the root cause of the problem.

Common Exceptions

Earlier in this post, I mentioned that some application crashes are avoidable if you know what to expect. Before showing you how to defend your application against exceptions. I’m going to list some of the more common ones and what causes them to be thrown.

FormatException

This exception is thrown when you try to convert a value into another data type, but the value is invalid for that type of data. The following code statement will throw a FormatException

 int test = int.Parse("DD");

DivideByZeroException

This one should be self-explanatory. This exception is thrown if you try to perform a calculation where you are dividing by zero. In the example code below, if the user enters zero for the number of months, the calculation will thrown this exception

 Console.WriteLine("What is the loan amount?");
 int loanAmount = int.Parse(Console.ReadLine());
 Console.WriteLine("How many months to pay it off?");
 int numberOfMonths = int.Parse(Console.ReadLine());
 
 int paymentAmount = loanAmount / numberOfMonths;

FileNotFoundException

This exception is thrown when your application is looking for a physical file that does not exist.

 string path = @"D:\GrandmasSuperSecretRecipes.txt";
 
 using (StreamReader reader = File.OpenText(path))
 {
  string textLine= ""; 
 }

Since my grandmother would never share her super-secret recipes with me, I don’t have the file this code is looking for, so a FileNotFoundException will be thrown.


ArgumentOutOrRangeException

This exception is thrown when you try to access an element of the collection that doesn’t exist. This common exception will often occur when looping through collections with FOR loops. If you don’t get the condition or iteration statement correct, you may run into this. The following code will throw the exception since it is looping more times than there are elements in the list.

 List<string> things = new List<string> { "dog", "whistle", "cat", "toy"};
 for (int i = 0; i < 5; i++)
 { 
 Console.WriteLine(things[i]);
 } 

There is also a risk of the exception being thrown when you use the CopyTo method of an array. If you try to copy the elements into an array without enough space, your code will throw this exception.

 string[] movieList = new string[]
 {"Avengers", "Goonies", "Notebook", "Halloween", "ET"};
 string[] newMovieList = new string[10];
 movieList.CopyTo(newMovieList, 8);

This code throws the exception because I am copying the elements from movieList into newMovieList but starting with the 9th element of the new array when copying. The two remaining elements are not enough for the five movie titles.


This exception is thrown when you are attempting to do a process that is not permitted. You will find this frequently occurs when learning about FOREACH loops. When you are using FOREACH to loop through a collection, you can not change that collection inside the loop. Attempting to do so will result in an InvalidOperationException.

 List<string> movieList = new List<string>() 
 {"Avengers", "Goonies", "Notebook", "Halloween", "ET"};
 
 foreach (string movie in movieList)
 {
 movieList.Add("Elf");
 }

Try…Catch Block

Now that you know what exceptions are, and some of the more common ones to watch out for, it’s time to learn how to “catch” exceptions and handle them in a way that provides your user with a better experience than that just crashing the application. It is the purpose of the TRY code block and the CATCH clause.


The TRY Block

Placing code inside a TRY block is a way to guard the application from code that could cause an exception to occur.

To create a TRY block, you simply use the TRY keyword with a set of curly brackets. The code you feel may cause a problem goes between the brackets.


TRY keyword

{

Potentially problematic code;

}

Let’s use an example from earlier in this post.

 int loanAmount = 48000;
 Console.WriteLine($"Your loan is for S{loanAmount}.");
 Console.WriteLine("How many months would you like to pay it off?");
 int numberofMonths; 
 numberofMonths = int.Parse(Console.ReadLine());
 int paymentAmount = loanAmount / numberofMonths;

This code example has a few places where something could go wrong. If the user a value that is not numeric, the Parse method will throw an exception. If they enter a zero value, our paymentAmount calculation will throw an exception. A TRY block would be ideal for this situation.


Ideally, you only want to put the code that could cause an exception in the TRY block.

 int loanAmount = 48000;
 Console.WriteLine($"Your loan is for S{loanAmount}.");
 Console.WriteLine("How many months would you like to pay it off?");
 int numberofMonths;
 
 try
 {
 numberofMonths = int.Parse(Console.ReadLine());
 
 int paymentAmount = loanAmount / numberofMonths;
 }

Now we have to defend our application from any problematic input from the user. However, we have not told the application what to do if an exception is thrown. For that, we will use the CATCH clause.


The CATCH Clause

The code inside the CATCH block will only be executed if an exception is thrown.


The syntax of the CATCH block is:


CATCH keyword(Exemption Type to Catch)

{

Code to handle the exception.

}

You can use multiple CATCH clauses to handled certain exception types differently by using exception filters. That will be discussed later in this post. For now, we are going to use the catch-all exception type “exception.”

 catch (Exception ex)
 {
 
 
 }

Any type of exception that occurs within the TRY block will be caught by this CATCH clause. The “ex” variable is the will hold the information of the exception so you can utilize the properties you read about earlier. Once the exception information is assigned to “ex,” the code inside the curly braces will execute. In this example, the only thing that happens is that the exception message is displayed in the console window.


Technically, there doesn’t have to be any code in the CATCH block for the exception to be caught. If an exception is thrown and the application control goes to an empty CATCH, it does nothing with the error, and the application continues to run. You may think this is a good thing, but it’s not.


This practice is known as “exception swallowing” or “error hiding.” It is considered an inferior coding practice. Handing an exception at the time it is thrown helps developers troubleshoot a problem at the place they occur. If you do nothing about the exception, the application would more than likely error out somewhere else because it did not have the data you were expecting.


Handling exceptions properly also allows you to inform the user in a friendly as to what has occurred and what they need to do to fix it. If you hide the error, the user will assume their data was entered and wonder why your application isn’t working correctly when the information doesn’t make sense farther down the line.


For our example, we are going to inform the user that they have entered invalid data.

 int loanAmount = 48000;
 Console.WriteLine($"Your loan is for S{loanAmount}.");
 Console.WriteLine("How many months would you like to pay it off?");
 int numberofMonths;
 
 try
 {
 numberofMonths = int.Parse(Console.ReadLine());
 
 int paymentAmount = loanAmount / numberofMonths;
 }
 catch (Exception ex)
 {
 Console.WriteLine("You have entered invalid data");
 }
 
 Console.WriteLine("Press any key to exit....");
 Environment.Exit(0);

Multiple Catch Blocks

Right now, our code has the potential to throw two different types of exceptions. We should have a different message for each exception so that the user knows what type of data they should have entered. You could do this with an IF statement that checks the exception information, but it’s better to catch the specific exception and handle it accordingly. You do this by replacing the general Exception type with the type of exception you want to catch.

 try
 {
 numberofMonths = int.Parse(Console.ReadLine());
 
 int paymentAmount = loanAmount / numberofMonths;
 }
 catch (DivideByZeroException ex)
 {
 Console.WriteLine("You entered 0 for the number of months.");
  Console.WriteLine("This would cause a calculation error.");
 }
 catch(FormatException ex)
 {
 Console.WriteLine("You entered a non-numeric number.");
 }

With this code, we provide the user with a different error dependent on the type of exception that was thrown. If we felt there was a chance of another exception being thrown that we haven’t anticipated, we can use the general Exception type to create a catch-all block as well.

 catch(Exception ex)
 {
 Console.WriteLine("An unexpected error occured! Contact support.");
 Console.WriteLine("Error message: " + ex.Message);
 }

You must place the “catch-all” block AFTER the specific CATCH blocks. When the application throws an error, it will read the code top-down, looking for a CATCH that handles the exception. If your general CATCH were first, every exception would be handled there, and the specific catches won’t be executed.


Nesting Try Catch

There will be times when a single TRY block won’t be enough to defend your application and respond to exceptions in a way that provides the best user experience. To demonstrate this, take a look at the following code example.

 int[] kidAges = new int[numberOfKids];
 
 for (int i = 0; i < kidAges.Length; i++)
 {
 Console.WriteLine($"What is the age of child #{i +1}?");
 
 kidAges[i] = int.Parse(Console.ReadLine());
 }

This code represents a simple FOR loop that asks for one piece of information. We know that the FOR loop can be problematic. We are going to use a TRY…CATCH to handle any unexpected errors.

 int[] kidAges = new int[numberOfKids];
 
 try
 {
 for (int i = 0; i < kidAges.Length; i++)
 {
 Console.WriteLine($"What is the age of child #{i + 1}?");
 
 kidAges[i] = int.Parse(Console.ReadLine());
 }
 }
 catch (Exception ex)
 {
 Console.WriteLine("An unexpected exception has occured. Contact support.");
 Console.WriteLine($"Error Message: {ex.Message}");
 goto ExitApplication;
 }

Now we will catch any unexpected exception that could be thrown. What about the exception that we can anticipate? We know that if the user does not enter an valid number the int.Parse method is going to throw an exception. Where is the best place to catch that exception.


Let's add a second CATCH block.

 try
 {
 for (int i = 0; i < kidAges.Length; i++)
 {
  Console.WriteLine($"What is the age of child #{i + 1}?");
 
 kidAges[i] = int.Parse(Console.ReadLine());
 }
 }
 catch (FormatException ex)
 {
 Console.WriteLine("You have entered an invalid number.");
 }
 catch (Exception ex)
 {
 Console.WriteLine("An unexpected exception has occured. Contact support.");
 Console.WriteLine($"Error Message: {ex.Message}");
 goto ExitApplication;
 }

If we handled the exception like this, the application would leave the loop when the exception is caught. This does not allow any type of “retry” logic to occur. Ideally we would next a TRY….CATCH inside of the TRY block for this loop.

 try
 {
 
 for (int i = 0; i < kidAges.Length; i++)
 {
 try
 {
 Console.WriteLine($"What is the age of child #{i + 1}?");
 
 kidAges[i] = int.Parse(Console.ReadLine());
 }
 catch (FormatException ex)
  {
 Console.WriteLine("Please enter a valid numeric number.");
 i--; 
 }
 
 }
 }
 catch (Exception ex)
 {
 Console.WriteLine("An unexpected exception has occurred. Contact support.");
 Console.WriteLine($"Error Message: {ex.Message}");
 goto ExitApplication;
 }

This updated code places a TRY…CATCH inside the FOR loop. This way, every input from the user will be protected against invalid data. If the user enters a non-integer value, a FormatException will be thrown. That exception will be caught inside the same loop and handled. To handle the exception, we are informing the user what they did wrong and reducing the loop iterator by one so that it reruns that loop

40 views0 comments

Recent Posts

See All