Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / exceptions

Using the Correct Exception Type for your Cause

4.64/5 (8 votes)
30 Oct 2011CPOL5 min read 28.6K  
When you have to raise an exception, there is always the question whether to create a new exception type or use one of the numerous ones in the .NET class library.
MSDN mainly differentiates between exceptions thrown by the runtime / system and by the application. Runtime and system are used as synonyms. Though many of these are specific to a certain purpose, some of them are generally reusable and may be kept at hand.

System.Exception

The base class of all exceptions. Any type thrown or caught must be derived from System.Exception. Many exceptions specific to a certain namespace in the class library directly derive on this exception type.
Throwing System.Exception directly is regarded bad practice as information about the exception cause is expected to be encoded into the exception type. Inside exception handling areas, this allows to separate the handlers for exceptions we sort of expected (e.g. a catch (RTFParserException ...)) and those we don't have an idea about - then Exception is caught.

System.ApplicationException : System.Exception

MSDN states that this exception type was originally intended as base for custom (user-defined) exceptions. This was generally not accepted by the "community" as this class is pretty useless. The idea of grouping exceptions through inheritance is generally good, however it makes more sense to use a base exception type inside the namespace of your module.

System.SystemException : System.Exception

SystemException serves as base class for all exceptions raised by the .NET runtime. It is discouraged to use this class directly, one of its derived classes should be used instead. Despite of sub-classing, no functionality is added to Exception.
Some of the exceptions derived on System.SystemException are only used by MSIL code (e.g. InvalidCastException) while others have a common use and therefore are used throughout the class library and may be raised in user code, too.
SystemExceptions map to HRESULT values that allow a meaningful "passage" of exceptions from .NET to COM and vice versa.

Using Exceptions as Reaction to Invalid Input


The following exceptions are typically thrown at the start of methods when the correctness of the parameters and internal state of the object is checked. The .NET class library makes extensive use of these exception types so developers expect them to occur. When providing code components to other developers, it seems sensible to fortify the implementation of interfaces with checks for the following conditions:

System.NotImplementedException : System.SystemException

This exception can be used to mark method stubs during development. Visual Studio typically inserts this exception in auto-generated event handlers.

System.ArgumentException : System.SystemException

This exception may be raised when there is a general problem with a method parameter. On the other hand, a more precise subclass can be used:

System.ArgumentNullException : System.ArgumentException

May be raised if a parameter is null while it should not be.

System.ArgumentOutOfRangeException : System.ArgumentException

May be raised if a parameter (e.g. an integer) that should be in a certain range is not.

System.FormatException : System.SystemException

Applies to method arguments that must follow a certain format (e.g. a string parameter containing a time stamp). In MSDN, this is described pretty much as an ArgumentException though it is not deriving from it.

System.RankException : System.SystemException

Applies to Array method arguments that must have a certain dimension but the actual given parameter has another dimension.

System.InvalidOperationException : System.SystemException

An operation can not be executed on the given internal state of the current object. Think of a cassette player where you cannot press play() while no cassette is inserted.

System.NotSupportedException : System.SystemException

An operation cannot be executed on the given type. Imagine a class implementing a certain interface which is generally functional but not able to execute a specific method.

Raising Exceptions as Reaction to Internal Bugs


While the exceptions discussed before are fit for signalling invalid input (that is caused by foreign code that is accessing your component in an unexpected/invalid way), they do not seem to be the right choice for checking your own work. Let's say your component enters an invalid state and the operation cannot be continued - however the input to your component was ok. In this case, it is not reasonable to throw a System.InvalidOperationException because it would imply that the caller has done something wrong.

For this case, it is advisable to have a custom exception type in your assembly namespace that informs about the problem, something like:

public class InternalMalfunctionException : System.Exception
{
   ...
}


Of course, exceptions raised this way do not prevent a fatal error to happen - but they help to keep the exception close to the error source (which may be helpful during debugging). In the same context, it might be sensible to check the output before returning from an operation and throw an exception like this when the operation results in invalid output (while the input was ok):

public class InvalidOutputException : System.Exception
{
   ...
}


When to Raise Exceptions


It is general consent that exceptions are to be raised in only in - um - exceptional situations, i.e. after something really bad happened that will spoil the day anyway. While the constraints are pretty clear for checking input and output to an operation, they are not so clear for exceptions raised because of internal bugs. When to raise such exceptions depends somewhat on gut-feeling - because when you already know the exception will be raised, it is time for a new code path instead (e.g. handling the case that a reference points to null).

Other scenarios like file opening show different shades of gray. Some methods raise an exception if a file cannot be opened, other implementations return a value indicating failure. Whether it is sensible to raise an exception is not depends on the context: The file may be or be not vital for the application, it may be specified by the program or by the user, etc.

Finally, scenarios including user input or data from an untrusted source do not call for exceptions. E.g. a user pressing a cancel button or selecting the wrong file in a file open dialog should not raise an exception (at least in production code). Another scenario would be parsing an HTML document of unknown origin - one could find anything in that.

Links



License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)