Exceptions are functionally identical to the counterpart found in C++ and Java. These
are mechanisms we use to be triggered at some condition — usually something bad. The Thanos of exceptions
from Java would be
NullPointerException, triggered whenever a piece of code attempts
to refer to a null pointer, or use a reference type variable that's null. There are a few standard issue
exceptions, including
IndexOutOfRangeException (for when you use a subscript value for
a container than is < 0 or >= the capacity of the container), or
DivideByZeroException (for when your program upsets mathematicians).
If your program doesn't implement a way to handle exceptions, there's a good chance it will crash "without
grace". Such as would be the case if the program just crashed with nothing more than a pop-up window stating that
the "application has stopped working." This type of program crash is just embarrassing, as compared to crashing
"with grace", where your application first presents some kind of diagnosis of what went wrong, and perhpas the
option to save the error log that can then be sent to the developer. While less than ideal, this at least
represents the developers' foresight and compentency to detect and compensate for something going wrong, as
opposed to the application performing
seppuku.
The
Exception class resides within the
System namespace that
we will probably be
using anyway. It is what we call
Serializable,
which means it can be written out to a data stream and easily reconstructed — a useful feature to include
with the aforementioned error logs your program might generate.
Exceptions have a
ToString() method that we'll use to dump out not just the exception type, but also a
brief explanation of what triggered the exception, which method call triggered it, and the parent method of where
it was triggered.
Let's first look at an example of how we might
throw an exception.
public class Slacker
{
public void doSomething()
{
if (you == "Pretty great!") // Which is always true
{
throw new AwesomeException("WARNING: You're too cool for school!");
}
}
}
Throwing an
Exception also has the effect of
terminating whatever method was currently being called, immediately returning control to the calling
method.
public class Slacker
{
public void doSomething()
{
if (you == "Pretty great!") // Which is always true
{
throw new AwesomeException("WARNING: You're too cool for school!");
}
}
public static void Main()
{
try // a "try" block is where you attempt some code that might generate an Exception
{
doSomething();
}
catch (AwesomeException except) // Followed by "catching" a possibly generated Exception
{
System.Console.WriteLine("Caught an AwesomeException: {0}", except);
// While I don't call "ToString()" explicitly, printing it will call it automatically
}
finally // Code we want executed unconditionally, with or without an Exception
{
airGuitar(); // This requires no explanation
}
}
}
You may have multiple
catch blocks for each of the types of
Exceptions any particular
try may generate, but
you will only have one
finally block. If you do have multiple
catch blocks, list the most specific type of
Exception
first. You may also elect to have an empty { }
catch block, if you would
rather simply ignore whatever
Exception was generated, although this isn't
often recommended. What is recommended is to try and limit the amount of code you toss inside of any
given
try block — you might feel the temptation to simply write
all of your code inside a huge
try block that attempts to catch
every type of
Exception under the sun, but this won't help you when it comes
to debugging your program later.
Here's a simple example of how we would build a custom
Exception:
public class MyNewException : Exception // Deriving from the "Exception" base class
{
public override string ToString()
{
return "This is the new error message, for MyNewException";
}
}