16.2. Handling Exceptions

When you include exceptions in your C# programs, you must decide what should take place once one is thrown. Some languages, like Java, require exceptions to be handled at the time of compiling. These are checked exceptions. When an exception is not handled in compiling, it is passed to runtime and called an unchecked exception. All exceptions in C# are unchecked. Therefore it is up to you, the programmer, to decide what to do to handle one when the need arises in runtime.

Here’s some advice to consider when contemplating when to handle an exception. This comes from Karl Seguin’s Foundations of Programming.

  1. Only handle exceptions that you can actually do something about.

  2. You can’t do anything about the vast majority of exceptions.

For example, if your code cannot connect to a database, there is probably nothing your program can do about it. However, as we allude to on the previous page, if you receive invalid input from a user, you can still throw an exception and re-prompt them to refine their input with an error message to help them get it right the next time.

16.2.1. Try/Catch/Finally

To handle exceptions in C#, we use the try/catch/finally construction. This functions very similarly to exception handling tooling in other languages. If you expect that a method might return an exception, the method call should be wrapped in a try statement. This could be because you threw the exception in the method yourself. Or you are in one of the common exception scenarios we outlined on the previous page.

If the code runs without throwing an exception, then the program continues as usual. However, if the try block results in an exception, then a later catch statement will be called. catch gives the program instructions on what to do in the event of an exception. The catch block prevents the program from stopping when it reaches the exception.

Here’s how we can update our Temperature constructor with a try/catch to handle the exception:

 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public Temperature(double fahrenheit)
{
   try
   {
      if (fahrenheit < absoluteZero)
      {
         throw new ArgumentOutOfRangeException("Value is below absolute zero");
      }
      else
      {
         Console.WriteLine(fahrenheit);
      }
   }
   catch(ArgumentOutOfRangeException e)
   {
      fahrenheit = absoluteZero;
      Console.WriteLine(e);
   }
}

First the constructor method comapres the fahrenheit input to absoluteZeroFahrenheit. If the fahrenheit is greater than absolute zero, the code runs without any exception. We tried the value and it passed so we can keep the code running.

If the action inside the try block results in an exception, i.e. the userTemp value is below absolute zero, the catch block is triggered. In this example, the catch block will reassign the inital value of fahrenheit to that of absolute zero. This particular catch block is activated because we set it to the ArgumentOutOfRangeException.

We assigned the ArgumentOutOfRangeException a variable of e in Line 21. In Line 23, the value of fahrenheit is being reassigned to absoluteZero. In Line 24, we are print the updated value via e. This provides feedback to the user about the error by displaying the exception or it’s message in the view of your running app. It also an option to rethrow an exception after it has been caught. You won’t need to rethrow exceptions in this course, but just know that it can be done.

Now, running the same sample input from the previous page does not output an exception:

Example

Input:

1
2
3
4
5
//Test 1: userTemp = 73
Temperature insideTemp = new Temperature(userTemp);

//Test 2: userTemp = -8200
Temperature outsideTemp = new Temperature(UserTemp);

Output:

You entered 73 degrees F.
-459.67

Although the exception has still been thrown, the try/catch construction diverts the program from terminating when it’s met.

Some try/catch blocks can also contain a finally statement that will run whether or not an exception was thrown. In this example, perhaps we want to communicate that if a Fahrenheit value is passed into the constructor that is less than absolute zero, then the fahrenheit input will be set to absolute zero.

 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Program
{
   public Temperature(double fahrenheit)
   {
      try
      {
         if (fahrenheit < absoluteZero)
         {
            throw new ArgumentOutOfRangeException("Value is below absolute zero");
         }
         else
         {
            Console.WriteLine("You entered " + fahrenheit + " degrees F.");
         }
      }
      catch(ArgumentOutOfRangeException e)
      {
         fahrenheit = absoluteZero;
         Console.WriteLine(e)
      }
      finally
      {
         Console.WriteLine("Fahrenheit cannot be less than -459.67.");
      }
   }
}

This finally statement is a tad redundant, since presumably a user will know this before trying to set the value. A more likely scenario to use a finally block might be in connecting to a database or other external service. For example, if a connection is opened within a try block and an exception is still caught, we’ll want to close the connection no matter what happens next.

16.2.1.1. What to Catch

When working with a try/catch statement, in statically-type languages like C#, you can declare the type of exception you wish to catch. Due to the four basic principles of object oriented programming (will explore this in the next lesson), we have to be aware that catching the base System.Exception type will result in all exceptions being caught. This is not advised. Be specific about the types of exceptions you want to catch, as we have in the example above. We will look at a few common exceptions in the next section.

If you have reason to believe that a given method may return an exception but you are unsure which type exactly, try/catch can — and should — include more than one catch block. Rather than catching one abstract exception type, you want to attempt to catch the exception with specificity so that the resulting decisions are meaningful. It is also important to note that order matters when it comes to catching. If the thrown exception matches the first catch block, then that block executes and any remaining catch blocks are ignored. If that exception thrown doesn’t match the first catch argument, then it goes on to the next statement to check for a type match.

Catching the base class Exception – that is, all exceptions – is sometimes referred to as exception swallowing. In these cases, exceptions are simply absorbed and not re-thrown or logged. If your program has a bug, or reaches an undesirable state, you want to know about it! Don’t swallow exceptions.

16.2.2. How to Avoid Exceptions

For some types of exceptions, there’s little you can do. If a database goes down, it’s down. However, many situations that result in exceptions are avoidable.

16.2.2.1. Validate User Input

Validate user input to ensure that it is of the type your code expects, and satisfies any other implicit constraints (such as numeric input falling within a certain range).

If you’re working within a framework such as ASP.NET, use the built-in validation capabilities to make this easier. We’ll cover these in detail when we discuss model validation.

Perhaps the most important thing to keep in mind here is that you should never assume that input given to your program is safe and valid. This is the case even when you’re providing browser-based validation. Clever (or malicious) users can bypass most forms of client-side validation.

16.2.2.2. Check For null References

In C#, null is a keyword that represents a null reference. A null reference is a reference to nothing, but not like an empty string or a value of zero. For example, a string variable can be set to null, but is not the same as an empty string.

Example

string n = null;
string e = "";
Console.WriteLine(n == e);

Output

False

Think of it as a placeholder for values or where values should be once a method runs, or a field is initialized, etc. Often you will see null values when working with databases.

You will see working examples of null when we learn more about Visual Studio and debugging (watch the autos and locals windows). You may read more on null and null references here.

If your code depends on an input parameter not being null to work properly, and it’s possible to gracefully handle the situation – for example, by re-prompting the user – then you should do so.

As with exceptions above, if there is no way to reasonably recover from a null pointer, then you shouldn’t swallow it. Furthermore, it’s generally a bad idea to catch a null pointer exception (NullReferenceException in C#). Read more on why this is the case.

16.2.3. Check Your Understanding

Question

Select an anomalous event when you may choose to not catch a thrown exception.

  1. None. All exceptions should be handled with catch.

  2. A database responsible for providing all of the image data on your site cannot be reached.

  3. A user inputs string data into a form designed to handle integers.

  4. It’s the bottom of the ninth and you just want the game to be over.

Question

True/False: Exception swallowing is a good choice to ensure no exceptions break your code.

  1. True

  2. False