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.
SystemException
s 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