Error Raising and Handling Guidelines
The following rules outline the guidelines for raising and handling errors:
All code paths that result in an exception should provide a method to check for success without throwing an exception. For example, to avoid a FileNotFoundException you can call File.Exists. This might not always be possible, but the goal is that under normal execution no exceptions should be thrown.
End Exception class names with the
Exception
** suffix as in the following code example.Public Class FileNotFoundException Inherits Exception ' Implementation code goes here. End Class [C#] public class FileNotFoundException : Exception { // Implementation code goes here. }
Use the common constructors shown in the following code example when creating exception classes.
Public Class XxxException Inherits ApplicationException Public Sub New() ' Implementation code goes here. End Sub Public Sub New(message As String) ' Implementation code goes here. End Sub Public Sub New(message As String, inner As Exception) ' Implementation code goes here. End Sub Public Sub New(info As SerializationInfo, context As StreamingContext) ' Implementation code goes here. End Sub End Class [C#] public class XxxException : ApplicationException { public XxxException() {... } public XxxException(string message) {... } public XxxException(string message, Exception inner) {... } public XxxException(SerializationInfo info, StreamingContext context) {...} }
In most cases, use the predefined exception types. Only define new exception types for programmatic scenarios, where you expect users of your class library to catch exceptions of this new type and perform a programmatic action based on the exception type itself. This is in lieu of parsing the exception string, which would negatively impact performance and maintenance.
For example, it makes sense to define a FileNotFoundException because the developer might decide to create the missing file. However, a FileIOException is not something that would typically be handled specifically in code.
Do not derive all new exceptions directly from the base class SystemException. Inherit from SystemException only when creating new exceptions in System namespaces. Inherit from ApplicationException when creating new exceptions in other namespaces.
Group new exceptions derived from SystemException or ApplicationException by namespace. For example, all System.IO exceptions are grouped under IOException (derived from SystemException) and all Microsoft.Media exceptions could be grouped under MediaException (derived from ApplicationException).
Use a localized description string in every exception. When the user sees an error message, it will be derived from the description string of the exception that was thrown, and never from the exception class.
Create grammatically correct error messages with punctuation. Each sentence in the description string of an exception should end in a period. Code that generically displays an exception message to the user does not have to handle the case where a developer forgot the final period.
Provide exception properties for programmatic access. Include extra information (other than the description string) in an exception only when there is a programmatic scenario where that additional information is useful. You should rarely need to include additional information in an exception.
Do not expose privileged information in exception messages. Information such as paths on the local file system is considered privileged information. Malicious code could use this information to gather private user information from the computer.
Do not use exceptions for normal or expected errors, or for normal flow of control.
You should return null for extremely common error cases. For example, a File.Open command returns a null reference if the file is not found, but throws an exception if the file is locked.
Design classes so that in the normal course of use an exception will never be thrown. In the following code example, a FileStream class exposes another way of determining if the end of the file has been reached to avoid the exception that will be thrown if the developer reads past the end of the file.
Class FileRead Sub Open() Dim stream As FileStream = File.Open("myfile.txt", FileMode.Open) Dim b As Byte ' ReadByte returns -1 at end of file. While b = stream.ReadByte() <> true ' Do something. End While End Sub End Class [C#] class FileRead { void Open() { FileStream stream = File.Open("myfile.txt", FileMode.Open); byte b; // ReadByte returns -1 at end of file. while ((b = stream.ReadByte()) != true) { // Do something. } } }
Throw the InvalidOperationException exception if a call to a property set accessor or method is not appropriate given the object's current state.
Throw an ArgumentException or create an exception derived from this class if invalid parameters are passed or detected.
Be aware that the stack trace starts at the point where an exception is thrown, not where it is created with the new operator. Consider this when deciding where to throw an exception.
Use the exception builder methods. It is common for a class to throw the same exception from different places in its implementation. To avoid repetitive code, use helper methods that create the exception using the new operator and return it. The following code example shows how to implement a helper method.
class File { string fileName; public byte[] Read(int bytes) { if (!ReadFile(handle, bytes)) throw NewFileIOException(); } FileException NewFileIOException() { string description = // Build localized string, include fileName. return new FileException(description); } }
Throw exceptions instead of returning an error code or HRESULT.
Throw the most specific exception possible.
Create meaningful message text for exceptions, targeted at the developer.
Set all fields on the exception you use.
Use Inner exceptions (chained exceptions). However, do not catch and re-throw exceptions unless you are adding additional information or changing the type of the exception.
Do not create methods that throw NullReferenceException or IndexOutOfRangeException.
Perform argument checking on protected (Family) and internal (Assembly) members. Clearly state in the documentation if the protected method does not do argument checking. Unless otherwise stated, assume that argument checking is performed. There might, however, be performance gains in not performing argument checking.
Clean up any side effects when throwing an exception. Callers should be able to assume that there are no side effects when an exception is thrown from a function. For example, if a Hashtable.Insert method throws an exception, the caller can assume that the specified item was not added to the Hashtable.
Standard Exception Types
The following table lists the standard exceptions provided by the runtime and the conditions for which you should create a derived class.
Exception type | Base type | Description | Example |
---|---|---|---|
Exception | Object | Base class for all exceptions. | None (use a derived class of this exception). |
SystemException | Exception | Base class for all runtime-generated errors. | None (use a derived class of this exception). |
IndexOutOfRangeException | SystemException | Thrown by the runtime only when an array is indexed improperly. | Indexing an array outside of its valid range:
|
NullReferenceException | SystemException | Thrown by the runtime only when a null object is referenced. | object o = null;
|
InvalidOperationException | SystemException | Thrown by methods when in an invalid state. | Calling Enumerator.GetNext() after removing an Item from the underlying collection. |
ArgumentException | SystemException | Base class for all argument exceptions. | None (use a derived class of this exception). |
ArgumentNullException | ArgumentException | Thrown by methods that do not allow an argument to be null. | String s = null;
|
ArgumentOutOfRangeException | ArgumentException | Thrown by methods that verify that arguments are in a given range. | String s = "string";
|
ExternalException | SystemException | Base class for exceptions that occur or are targeted at environments outside of the runtime. | None (use a derived class of this exception). |
COMException | ExternalException | Exception encapsulating COM Hresult information. | Used in COM interop. |
SEHException | ExternalException | Exception encapsulating Win32 structured Exception Handling information. | Used in unmanaged code Interop. |
Wrapping Exceptions
Errors that occur at the same layer as a component should throw an exception that is meaningful to target users. In the following code example, the error message is targeted at users of the TextReader class, attempting to read from a stream.
Public Class TextReader
Public Function ReadLine() As String
Try
' Read a line from the stream.
Catch e As Exception
Throw New IOException("Could not read from stream", e)
End Try
End Function
End Class
[C#]
public class TextReader
{
public string ReadLine()
{
try
{
// Read a line from the stream.
}
catch (Exception e)
{
throw new IOException ("Could not read from stream", e);
}
}
}
See Also
Design Guidelines for Class Library Developers | Handling and Throwing Exceptions