Error Handling: Exceptions

Error Handling: Exceptions

The Crash Course

  • When things go wrong, exceptions provide a way to have the part of the code that discovered the problem indicate that something has gone wrong, and return execution back to (hopefully) some other part of the code that knows how to deal with the problem that was reported.
  • If you know you are running code that could potentially cause a problem, you wrap it in a try-catch block, putting the type of exception in the parentheses of the catch block: try { /* code that may throw an exception here */ } catch(Exception e) { /* code to handle the problem here */}
  • You don't just need to use the Exception class; you can use a type derived from the Exception class, like say, the FormatException class. When you do this, only exceptions of the right type will be handled, and others will continue to be "thrown" up to higher levels of the call stack.
  • You can string together multiple catch blocks, going from most specific to least specific types of exceptions: try { } catch(FormatException e) { /* handle format errors here */ } catch(Exception e) { /* handle all other errors here */ }
  • You can create your own types of exceptions by deriving from the Exception class.
  • To actually start off the process in your own code, you use the throw keyword: throw new Exception("An error has occurred.");

Introduction

Sometimes things go wrong. It happens. Maybe the user typed in a letter when they should have typed in a number. Maybe the user clicked on the wrong buttons, in exactly the wrong order. Maybe a butterfly flapped it's wings, causing a hurricane (or typhoon, if you live in those parts of the world), which caused an earthquake, which caused a tree to fall directly onto your network cable, destroying it (all, somehow, without you knowing it!).

The point is, anything can go wrong, and in this tutorial, we'll talk about how to handle it gracefully—meaning, that it is done in a way that minimizes the effect on the user and allows the section of code that caused the problem to resolve it.

Let's take the example of the user typing in bad input, like a letter or text when they were supposed to provide a number. So perhaps our starting code is something like this:

Console.Write("Enter a number:  ");
string userInput = Console.ReadLine();
int number = Convert.ToInt32(userInput);

If you don't already know what happens when you type in, say "abc", instead of a number, go ahead and give it a try.

If you are running in debug mode, the program will freeze, and Visual C# Express will pop up at the location that there was a problem—on the Convert.ToInt32 line. If you are running in "normal", non-debug mode, your program will just simply die.

We've ran into a problem, and we need a way to intelligently deal with it.

Fortunately for us, C# has a built-in way of taking care of things like this: exceptions.

Exception handling is a powerful tool for handling errors. Let's say that you have a method that is trying to do something (like lower Batman into a shark filled tank), and it discovers that there's something horribly wrong (like somehow, Batman has escaped… again…). At this point, everything is in a terrible state, and the method that discovered it doesn't necessarily know what caused the problem, or how to fix it. It just knows it can't proceed.

Exceptions provide a way for any method to say "I've run into a problem and I can't continue in any intelligent way." This error, along with possibly some information about the problem is sent back to the place that the method or code was called from, hoping that someone else knows what to do with the problem.

If something knows what to do, it intercepts the message and does something useful. Perhaps that means telling the user that there's a problem and trying again.

If nothing is able to handle the problem, it keeps going up to higher and higher method calls, all the way until it gets back to the Main method. If even that doesn't handle the error, the program terminates. (And the user is left wondering how the heck they'll ever recover their lost file.)

The idea, though, is that when a problem comes up, something will be able to handle it, and the program will be able to continue in some sort of fashion, despite the problem, because the exception was handled.

"Catching" Exceptions

Exceptions are kind of like the game of Hot Potato. If you've never played the game before, the concept is simple. A small group of people stand around in a circle, throwing an object (called the "potato", whether it is an actual potato or not) around from person to person, as quickly as possible. In the game, there's music playing, and when it ends, whoever has the potato in their hands loses. So you try to throw the potato away to the next person as quickly as possible.

When there's a problem in your program, and something bad happens and the computer just doesn't have a clue how to fix it, an error message, packed neatly into an object of the type Exception is created and thrown very quickly up the chain to the method that called it. The method that the problem gets called in doesn't finish running, nor does it return a value. It just stops dead in its tracks, creates the exception, and goes back to the place that called it.

Once it gets there, if that method doesn't handle the problem, it takes the exception and, just as quickly, throws it on to the method that called it. This keeps happening, up until someone finally says "I know what to do with this problem" or until it reaches your Main method and gets thrown again, which causes the program to simply die. (Or, again, if you are running in debug mode, it will open up Visual C# Express right there, where the problem ("error") first occurred.

So what we want to do is figure out who is responsible for handling the problem, and find a way to "catch" the exception and do something intelligent with it.

(By the way, I'm not making this "throw", "catch" stuff up. That's actually the terms that programmers use for this.)

We can do this with what is called a try-catch block. This introduces two new keywords that we haven't seen yet: try, and catch. Basically, what we're going to do is say, "I've got this code that I know may result in a problem. Try to run it, and if there's an error or exception, catch the exception, and handle it using this other block of code."

This is done like this:

Console.Write("Enter a number:  ");
string userInput = Console.ReadLine();
 
try
{
    // The code that may have problems goes in here...
    int number = Convert.ToInt32(userInput);    
 
    // It is important to point out that if there was an error in 
    // the line above, the program would jump down to the
    // catch block below, and any code right here would not
    // be executed.
}
catch(Exception e)
{
    // The code to handle the problem goes here.
    // Notice that if nothing goes wrong, this code
    // never gets executed.
    Console.WriteLine("You must enter a number.");
}

So, in short, you take your "dangerous" code, place it inside of a try block, and then immediately after the try block, you put a catch block that has code to handle the problem. If something goes wrong in the middle of the "dangerous" code, the rest of that dangerous code won't get executed. It jumps down to the error handling code in the catch block. On the other hand, if everything goes as planned, the stuff in the catch block will never be executed.

Take another look though, at the stuff in parentheses at the start of the catch block: Exception e. There are two things that we should bring up here. One, in this line, we're creating a variable named e (you could call it exception, or cheeseburger, or whatever you want) that you can then use inside of the catch block. For instance, the Exception class has a property called Message, so you could say Console.WriteLine(e.Message); and print out that message. The Exception object that you get here may be storing information about what went wrong, and you can look at it to figure out what happened.

Second, when an exception is thrown, it may be using the Exception class, or potentially, a derived class that is more specific to the type of problem that occurred. Going back to our bad user input example, this isn't actually throwing an Exception object, it is throwing a FormatException object, which is derived from the Exception class. (Well, wait, I'd better be more specific. Contrary to what I just said, technically, it is throwing an Exception, but more specifically, it is throwing a FormatException, which is a type of Exception.)

In the code we looked at, the Convert.ToInt32 method can throw a FormatException, or an OverflowException, which happens when you give it text that is, in fact, a number (or we'd get the FormatException) but is way too big to put into an int. Something like "1038493982582058259250" would do it.

In the catch block, we could say catch(Exception e), and catch any thing that is, or is derived from the Exception class (which is everything) or we could say catch(FormatException formatException), and only catch things that are of the type FormatException—any OverflowException that is thrown will be sent on, as though it was unhandled (because it wasn't) up to the calling method, then to the one that called that, and so on, until it is handled in a separate try-catch block, or thrown out to the Main method, killing the program.

So we can be selective about what exceptions to handle.

Not Giving the Exception Variable a Name

I mentioned earlier that with a catch block, that you can use the exception you're catching, (for example, Exception e) as a variable inside of the catch block.

If, on the other hand, you aren't going to use it, you don't need to even give the variable a name, like this:

try
{
    //...
}
catch(Exception) // this Exception variable has no name
{
    //...
}

So, on one hand, now you can't refer back to the exception inside of the catch block, but on the other hand, if you aren't going to use it, that's not a problem. This frees up whatever name you would have chosen to be used by a different variable, and it makes it so you don't get a compiler warning that says something like "the variable e is declared but never used".

Handling Different Exceptions in Different Ways

Like we just discussed, different blocks of code can throw different exceptions. You can catch any of the different types, or you can catch them all (Pokemon]] theme music fills the air) with catch(Exception e), or you can choose which derived type you want to catch.

But what if you want to catch them all, but handle each different kind of error differently?

There's an easy way to do this:

try
{
    int number = Convert.ToInt32(userInput);
}
catch (FormatException e)
{
    Console.WriteLine("You must enter a number.");
}
catch (OverflowException e)
{
    Console.WriteLine("Enter a smaller number.");
}
catch (Exception e)
{
    Console.WriteLine("An unknown error occurred.");
}

As you can see, you just put multiple catch blocks at the end of the try block.

When you do this, though, only one of the blocks can actually get executed. Once the exception is handled, it will skip the rest of the catch blocks. So if a FormatException is thrown, it will enter the first block and run that code to handle the problem, but the catch(Exception e) block will not get executed, even though the exception was technically of the type Exception. It has already been handled.

Doing this, though, makes it so you can handle different types of exceptions. You just need to be sure to put the more specific type--the derived type--before the more general, base type. If your first block was the catch(Exception e) block, it would catch everything, and nothing would ever get into the FormatException or OverflowException blocks. So ordering is important.

Throwing Exceptions

So far, we've just talked about handling exceptions. But what if you are writing some of your own code, and you are aware of a potential problem that could occur that could stop you dead in your tracks? You can create and throw your own exceptions!

To throw an exception, simply use the throw keyword:

public void CauseProblemsForTheWorld()  //always up to no good...
{
    throw new Exception("Just doing my job!");
}

So it kind of feels like the return statement, but it, of course, throws exceptions instead of returning a value. You create the Exception object, just like any other object, with the new keyword, and away it goes.

Of course, you're not required to just simply use the Exception class. There's plenty of other options already defined. For instance, here's a small collection of some common exceptions and what they're for:

Exception Type What it Does
NotImplementedException If a method has been defined, but not implemented yet, you can use this exception. If Visual C# Express automatically generates methods for you, it will put this in the body.
IndexOutOfRangeException You tried to access an array or something at an index that is beyond how big the array is.
InvalidCastException You tried to cast something to another type, but the type you tried to cast to wasn't the right kind of object.
FormatException The text you had is not in the right format for converting to something else (like letters in a string that is supposed to be turned into a number).
NotSupportedException You tried to do an operation that wasn't supported. For instance, make a method call at a time that didn't allow it.
NullReferenceException A variable for any kind of object actually contained null instead of an actual object, but whatever you were doing required something besides null.
StackOverflowException You see this all the time when you run out of space on the stack from calling too many methods. This is usually a result of recursion that went bad.
DivideByZeroException You tried to divide by zero and got caught.
ArgumentException One of the parameters or arguments you sent to a method didn't match what was required by the method.
ArgumentNullException One of the arguments or parameters that you gave to a method was null, but the method requires something besides null.
ArgumentOutOfRangeException One of the arguments contained a value that the method couldn't intelligently do something with. For instance, if a method required a number between 1 and 10, but you gave it -13, you might see this kind of exception.

And of course, if you don't see the type of exception you want, you can always create your own. All you need to do is create a class that is derived from the Exception class, or alternatively, derives from a class that is derived from the Exception class. (For example, you could derive from the ArgumentNullException class if you want.)

This will look something like this:

// For all of you on the other side of the pond, if it makes 
// you feel any better, you can still call them "beef burgers" if you want!
public class AteTooManyHamburgersException : Exception 
{
    public int HamburgersEaten { get; set; }
 
    public AteTooManyHamburgersException(int hamburgersEaten)
    {
        HamburgersEaten = hamburgersEaten;
    }
}

With this class, you can how say:

throw new AteTooManyHamburgersException(125);

And:

try
{
    EatSomeHamburgers(32);
}
catch(AteTooManyHamburgersException hamburgerException)
{
    Console.WriteLine(hamburgerException.HamburgersEaten + " is too many hamburgers.");
}

Wow. I never thought I'd see the day where I had the word "hamburgerException" in my code. Perhaps we're nearing the End of Times after all.

The finally Keyword

There's one last thing we want to take a look at, regarding exceptions: The finally keyword. At the end of a try-catch block, you can have a finally block (turning it into a try-catch-finally block). The code inside of the finally section will get executed no matter what. If it ran through the try block normally, it will get executed. If there was an exception, it will get executed.

Here's an example of how this works:

try
{
    // Do some stuff that might throw an exception here
}
catch (Exception)
{
    // Handle the exception here
}
finally
{
    // This code will always get execute, regardless of what happens in the try-catch block.
}

Now, you actually might be wondering why you can't just put the code that is in the finally block after the try-catch block, just by itself? Why can't I just do something like this:

try
{
    // Do some stuff that might throw an exception here
}
catch (Exception)
{
    // Handle the exception here
}
 
// This code will always get execute, regardless of what happens in the try-catch block.

You're right. It would also get executed no matter what happens in the try-catch block. But there's a subtle difference. The stuff in the finally block will get executed, even if you're use the return keyword in there anywhere. Look at this code, for an example of what I mean:

public static void TestFinally(bool throwException)
{
    try
    {
        Console.WriteLine("in try");
 
        if (throwException)
        {
            throw new Exception();
        }
 
        return;
    }
    catch (Exception)
    {
        Console.WriteLine("in catch");
    }
    finally
    {
        Console.WriteLine("in finally");
    }
}

If you run this code, you'll see that if the flow of execution is normal, and there's no errors, you'll hit that return statement in the try block. But on its way out, before actually returning, the stuff in the finally block will get executed.

One thing this means is that you can't put return statements inside of finally blocks. That's just not how they work—it could already be returning when it gets executed.

This brings up an interesting thing, though. Somehow, finally blocks got attached to exception handling. Many, many books out there treat it simply as a part of exception handling. As a way to ensure that a chunk of code always gets executed, regardless of whether there was an error or not.

But that's not exactly what it is about. If that's what you want, you can simply put that code after the try-catch block has completely ended.

It is more about running certain code, no matter how you return from a method. You could return normally, or in an error state, or you could have a dozen different ways of returning normally from a method, but forcing certain code to be executed all the time. For instance, you could do something crazy like this:

private int numberOfTimesMethodHasBeenCalled = 0;
 
public static int RandomMethod(int input)
{
    try
    {
        if(input == 0) return 17;
        if(input == 1) return -2;
        if(input == 2) return -11;
        if(input == 3) return 8;
        if(input == 4) return 6;
 
        return 5;
    }
    finally
    {
        numberOfTimesMethodHasBeenCalled++;
    }
}

In this case, no matter how we return from the method, the code in the finally block will always get executed. Now, yeah, you could write this in a different way, and avoid the try-finally block altogether. (For instance, just putting numberOfTimesMethodHasBeenCalled++; at the start of the method would do it in this case.)

There's always lots of ways to write the same block of code.

What's Next?

Error handling with exceptions may not seem intuitive or easy to use, at first glance. There's all of this try-catch stuff floating around, and things often seem to feel more complicated than it needs to be. But don't worry; it's the kind of thing that you can learn a little at a time.

Try using try-catch blocks when you know there could potentially be a problem, or if you see your program crash, when it throws an exception.

Error handling like this allows you to handle any problems that might come up, and maybe most importantly, it allows the methods that discover the problem to be able to hand the problem off to the place that caused the problem, or at least, to the place that can fix it.

Up next, we'll take a look at a way to treat methods as though they are objects, allowing us to pass them around as needed: delegates. Delegates will be an important part of where we'll go after that: events, which allow one part of our code to alert another part when certain things occur.