Only throw exceptions in exceptional situations.
Do not throw exceptions in situations that are normal or expected (e.g. end-of-file). Use return values or status enumerations instead. In general, try to design classes that do not throw exceptions in the normal flow of control. However, do throw exceptions that a user is not allowed to catch when a situation occurs that may indicate a design error in the way your class is used.
Do not throw exceptions from inside destructors.
When you call an exception from inside a destructor, the CLR will stop executing the destructor, and pass the exception to the base class destructor (if any). If there is no base class, then the destructor is discarded.
Only re-throw exceptions when you want to specialize the exception.
Only catch and re-throw exceptions if you want to add additional information and/or change the type of the exception into a more specific exception. In the latter case, set the InnerException property of the new exception to the caught exception.
List the explicit exceptions a method or property can throw.
Describe the recoverable exceptions using the <exception>
tag.
Explicit exceptions are the ones that a method or property explicitly throws from its implementation and which users are allowed to catch. Exceptions thrown by .NET framework classes and methods used by this implementation do not have to be listed here.
Always log that an exception is thrown.
Logging ensures that if the caller catches your exception and discards it, traces of this exception can be recovered at a later stage.
Provide a method or property that returns the object’s state.
For example, consider a communication layer that will throw an InvalidOperationException
when an attempt is made to call Send()
when no connection is available. To allow preventing such a situation, provide a property such as Connected
to allow the caller to determine if a connection is available before attempting an operation.
Use standard exceptions.
The .NET framework already provides a set of common exceptions. The table below summarizes the most common exceptions that are available for applications.
EXCEPTION | CONDITION |
ApplicationException | General application error has occurred that does not fit in the other more specific exception classes. |
IndexOutOfRangeException | Indexing an array or indexable collection outside its valid range. |
InvalidOperationException | An action is performed which is not valid considering the object’s current state. |
NotSupportedException | An action is performed which is may be valid in the future, but is not supported. |
ArgumentException | An incorrect argument is supplied. |
ArgumentNullException | An null reference is supplied as a method’s parameter that does not allow null. |
ArgumentOutOfRangeException | An argument is not within the required range. |
Throw informational exceptions.
When you instantiate a new exception, set its Message
property to a descriptive message that will help the caller to diagnose the problem. For example, if an argument was incorrect, indicate which argument was the cause of the problem. Also mention the name (if available) of the object involved.
Also, if you design a new exception class, note that it is possible to add custom properties that can provide additional details to the caller.
Throw the most specific exception possible.
Do not throw a generic exception if a more specific one is available (related to Section 6.08).
Say that you are trying to write to a file on the hard drive but that file is opened by another process. It will generate an error. Don’t catch it then throw a generic error.
Only catch the exceptions explicitly mentioned in the documentation.
When using an object consider catching only the exception that are documented. In the case that you are using the SendAsync method of the HttpClient[9] object, the documentation[9] shows you would only explicitly catch ArgumentNullException, InvalidOperationException and HttpRequestExeption.
Moreover, do not catch the base class Exception
or ApplicationException
. Exceptions of those classes generally mean that a non-recoverable problem has occurred.
Ref: Microsoft Docs, Jun 2019, HttpClient.SendAsync Method, https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.sendasync?view=netframework-4.8#System_Net_Http_HttpClient_SendAsync_System_Net_Http_HttpRequestMessage_System_Net_Http_HttpCompletionOption_
Derive custom exceptions from ApplicationException.
All exceptions derived from SystemException
are reserved for usage by the CLR only.
Provide common constructors for custom exceptions.
It is advised to provide the three common constructors that all standard exceptions provide as well. These include:
XxxException()
XxxException(string message)
XxxException(string message, Exception innerException)
Avoid side-effects when throwing recoverable exceptions.
When you throw a recoverable exception, make sure that the object involved stays in a usable and predictable state. Usable means that the caller can catch the exception, take any necessary actions, and continue to use the object again. With predictable is meant that the caller can make logical assumptions on the state of the object.
For instance, if during the process of adding a new item to a list, an exception is raised, then the caller may safely assume that the item has not been added, and another attempt to re-add it is possible.
Do not throw an exception from inside an exception constructor.
Throwing an exception from inside an exception’s constructor will stop the construction of the exception being built, and hence, preventing the exception from getting thrown. The other exception is thrown, but this can be confusing to the user of the class or method concerned.
Throw exceptions rather than returning some kind of status value
A code base that uses return values for reporting the success or failure tends to have nested if-statements sprinkled all over the code. Quite often, a caller forgets to check the return value. Structured exception handling has been introduced to allow you to throw exceptions and catch or replace exceptions at a higher layer. In most systems it is quite common to throw exceptions whenever an unexpected situations occurs.
Throw the most specific exception that is appropriate
For example, if a method receives a null argument, it should throw ArgumentNullException instead of its base type ArgumentException.
Don’t swallow errors by catching generic exceptions
Avoid swallowing errors by catching non-specific exceptions, such as Exception, SystemException, and so on, in application code. Only top-level code, such as a last-chance exception handler, should catch a non-specific exception for logging purposes and a graceful shutDown of the application.
Properly handle exceptions in asynchronous code
When throwing or handling exceptions in code that uses async/await or a Task remember the following two rules
- Exceptions that occur within an async/await block and inside a Task’s action are propagated to the awaiter.
- Exceptions that occur in the code preceding the asynchronous block are propagated to the caller.