Airbrake Blog

.NET Exception Handling: Exception Class Hierarchy

Written by Frances Banks | Feb 23, 2017 1:29:17 PM

.NET is among the most robust software frameworks on the market today, yet with that awesome power, comes the responsibility of managing a huge variety of exceptions. Given that .NET enabled many different languages across all sorts of platforms and devices, .NET must be capable of handling errors that deal with all manner of issues, from web traffic and I/O to operating system problems and database exceptions. In fact, the latest version of .NET has built-in classes to handle over 300 unique exceptions in total!

Managing all these exceptions obviously presents a challenge, so Microsoft implemented a namespace system within the .NET framework. A namespace is basically just a unique name, which can be assigned to various objects or components, in order to more easily categorize and organize them.

In the case of the .NET framework, namespaces are used to provide simple hierarchical structures to the thousands of classes that exist. For example, the System namespace is the senior-most namespace in the entire framework: every other class and/or namespace in .NET is in some way derived from (a child of) the System namespace.

Namespaces are then indicated by separating namespaces and classes with a series of chained periods (.). For example, the Object class is part of the System namespace, so the full inheritance hierarchy is: System.Object.

Whew! With the basics of how .NET uses namespaces out of the way, we can dive into the exception class hierarchy. Since there are hundreds of exceptions in total and an overview of every possible exception namespace is pointless, we'll just examine a few fundamental exception namespaces and their relative hierarchy, as shown in the official documentation.

Here's the basic list of .NET exception types, along with their base type that they inherit from:

Exception type Base type
Exception Object
SystemException Exception
IndexOutOfRangeException SystemException
NullReferenceException SystemException
AccessViolationException SystemException
InvalidOperationException SystemException
ArgumentException SystemException
ArgumentNullException ArgumentException
ArgumentOutOfRangeException ArgumentException
ExternalException SystemException
COMException ExternalException
SEHException ExternalException

Below we'll examine each of these core exception types provided by .NET, looking at why and when they might be raised during normal execution.

Exception

The bread and butter of all .NET exceptions, as the name suggests, the Exception class is the base class from which all other exceptions inherit. As with many exception handlers in other programming languages, the Exception class provides a number of useful properties that assist with exception handling:

  • StackTrace: A stack trace to see where exactly the exception occurred.
  • InnerException: Useful when exceptions chain off one another. This allows one type of exception to throw another type of exception, ad infinitum.
  • Message: The detailed message indicating what happened.
  • Data: A dictionary that can be used to store arbitrary data associated with this particular exception instance.

SystemException

SystemException houses all exceptions related to, well, the system. These include all runtime-generated errors, such as System.IO.IOException, which is thrown when an I/O error occurs.

In short, SystemException is the base class that all non-application errors inherit from. If your application code screws up somehow, that's handled by another exception type, but if the system screws up, it's on SystemExceptionto sort it out.

IndexOutOfRangeException

The IndexOutOfRangeException is thrown anytime an array or collection is accessed using an index outside its bounds.

NullReferenceException

If you attempt to use an object that is considered a null object, the NullReferenceException will be thrown.

AccessViolationException

An AccessViolationException is thrown when unmanaged code attempts to access memory which is unallocated. For many .NET applications, this will never occur, due to how .NET handles managed vs unmanaged code.

Managed code is code that .NET compiles and executes using the common language runtime. Conversely, unmanagedcode compiles into machine code, which is not executed within the safety of the CLR.

Many languages that rely on .NET, such as C# and Visual Basic, are entirely compiled and executed in the CLR. This means that code written in C# is always managed code and can, therefore, never throw an AccessViolationException.

However, a language like Visual C++ does allow unmanaged code to be written. In such cases, it's entirely possible to access unallocated memory, and throw an AccessViolationException.

InvalidOperationException

InvalidOperationException is a pretty cool and somewhat intelligent exception. It is typically thrown when the state of an object cannot support the particular method call being attempted for that object instance.

For example, when the .MoveNext method is called on an enumerable object after elements in that collection have been modified. Since MoveNext can no longer determine which object should be next up, it throws an InvalidOperationException.

ArgumentException

As the name implies, ArgumentException is thrown when a call is made to a method using an invalid argument.

One interesting note is that since an executing program is not intelligent enough to deduce whether or not a provided argument is valid for the context of the current execution, most of the time an ArgumentException is thrown, it's because a developer placed it there intentionally, to be thrown in a particular situation.

For example, if we create a FullName(string first, string last) method, we're expecting a first and last name to be passed as arguments, both of which are strings. However, we may also have other requirements, like neither argument may contain any unexpected punctuation. We can purposely add logic into our code that validates the arguments that were passed, and if we determine the first name provided is invalid, we intentionally throw an ArgumentException. By doing so, this ensures that we (or any other developer that uses our method) know if we're trying to pass invalid arguments to our method.

ArgumentNullException

ArgumentNullException is inherited from ArgumentException, but is thrown specifically when a method is called that doesn't allow an argument to be null.

ArgumentOutOfRangeException

Another child of ArgumentException, the ArgumentOutOfRangeException error is thrown when a method expects argument values within a specified range, yet the provided argument falls outside those bounds.

ExternalException

ExternalException is the base exception used for any error occurring externally, outside the bounds of your own application. Typically, this means that it's used when there's a problem communicating with a web address, database, and the like.

COMException

Examining ComException requires a bit of history, so please bear with me. Prior to the creation of the .NET framework, way back in 1993, Microsoft created and begin using the Component Object Model (COM) binary-interface, which effectively allowed code to be used across languages and environments, in an object-oriented fashion.

COM handled exceptions through a property called HResult (or result handler), which is a 32-bit value that combines a few fields together into a simple string. The encoded HResult would tell the application which exception was thrown and the application could respond appropriately.

About a decade after COM was introduced, .NET was first released and it quickly began taking over as a much more powerful programming framework, and thus, the usage of COM quickly died out.

Unlike COM, .NET is far more complex and primarily uses direct object-oriented handling of Exception classes for all exception management. However, in order to provide backward compatibility, .NET provides access to HResultthrough the Exception.HResult property. Each exception in .NET is mapped to a distinct HResult value. When managed code throws an exception, the common language runtime finds the appropriate exception class based on the HResult property, allowing COM objects to deal with normal, useful .NET exception classes rather than convoluted HResults.

In cases where .NET sees an HResult that it's unfamiliar with, a COMException is thrown.

SEHException

Also inherited from ExternalException, an SEHException acts as a sort of "catch-all" for unmanaged code exceptions which have not already been mapped to an existing .NET exception class. SEH, in this instance, stands for structured exception handling, which is a mechanism used in .NET for handling both hardware and software exceptions.

Since an SEHException requires an exception from unmanaged code that has not already been mapped to an existing exception type which might better suit it, throwing of an SEHException is rather rare.

To get the most out of your own applications and to fully manage any and all .NET Exceptions, check out the Airbrake .NET Bug Handler, offering real-time alerts and instantaneous insight into what went wrong with your .NET code, along with built-in support for a variety of popular development integrations including: JIRA, GitHub, Bitbucket, and much more.