Exception handling is the technique of handling runtime errors in your application code. Basically, you have two categories of exceptions: Exceptions that are generated by the application and exceptions that are generated by the runtime. Exceptions should be handled with care. You should have a good idea of how exceptions should be handled and when they are needed to be handled in your code. In this post, I will present a few tips and best practices for working with exceptions in C#.
The base class for all exceptions in .NET is Exception. All exception classes in the exception hierarchy derive directly or indirectly from this class. The ApplicationException and SystemException classes are derived from the Exception class, and the Common Language Runtime (CLR) throws an instance of a type that is derived from SystemException when an error occurs at runtime. Note that you should never catch SystemException or throw an instance of SystemException in your application’s code. The reason is that doing so may inadvertently hide run-time problems, such as COM interop or out-of-memory exceptions.
And now for our guidelines…
Always derive from the Exception class
When creating custom exception classes, always derive from the Exception class, not from the ApplicationException class. One of the reasons for this is that an instance of ApplicationException is thrown only by the application and never by the runtime. Whenever you throw an instance of ApplicationException in your code, you merely increase the call stack without adding much value.
Handle exceptions at the highest level
Note that exceptions are bubbled up to the higher level in the method call hierarchy, and it is not a good practice to handle exceptions in all the layers of your application. You should handle an exception as high up in the call hierarchy as you can. For example, you can consume an exception in the presentation layer and display appropriate messages to the user to communicate the exact error that has occurred.
Use predefined exceptions and clear error messages
It is a good practice to use specific exceptions like FileNotFoundException and IOException when writing exception handlers and then a general catch block at the end with the Exception class. This will ensure that you get a clear understanding of the exact error that has occurred. As Microsoft’s documentation states: “The ApplicationException class does not provide information as to the cause of exceptions. In most scenarios, instances of this class should not be thrown. In cases where this class is instantiated, a human-readable message describing the error should be passed to the constructor.”
Use try/catch/finally blocks to handle exceptions
You should use try/catch blocks to handle exceptions and use a finally block to clean up the resources used in your program. The try block should contain code that might raise an exception, the catch block should be used to handle the exception thrown inside the try block, and the finally block should be used to de-allocate any resources the program has used. Note that the finally block is guaranteed to be executed regardless of whether an exception has occurred. Hence, the finally block is the best place in your code for cleaning up the resources your program has used.
The code snippet below shows how the “using” statement can be used to dispose of resources. Note that the “using” statement is the equivalent of a try/finally block.
public string Read(string fileName)
{
try {
string data;
using (StreamReader streamReader = new StreamReader(fileName))
{
data = streamReader.ReadToEnd();
}
return data;
}
catch (Exception)
{
throw;
}
}
Avoid throwing and re-throwing exceptions
Throwing exceptions is expensive. It is not only good practice to avoid throwing exceptions, but it is bad practice to re-throw exceptions. In addition to incurring processing overhead that could impact application performance, re-throwing exceptions will cause you to lose the stack trace. Hence, do not re-throw an exception as shown in the following code snippet…
try
{
//Some code that might throw an exception
}
catch(Exception ex)
{
throw ex;
}
Instead, just use the statement “throw” as shown below. If you use “throw” without specifying the exception, then the exception will not be handled in your exception handler but will propagate upwards in the call hierarchy.
try
{
//Some code that might throw an exception
}
catch(Exception ex)
{
throw;
}
You could also write the preceding code in a more concise way:
try
{
//Some code that might throw an exception
}
catch
{
throw;
}
It should be noted that there are special cases where re-throwing exceptions is standard practice. For example, you may need to re-throw an exception when you want to roll back a database transaction.
Never swallow exceptions
Never swallow exceptions. That is, you should never hide the error that has occurred. It is a good practice to log exceptions in your application. When logging exceptions, you should always log the exception instance so that the log includes the complete stack trace and not the exception message only. Here is an example that illustrates this.
try
{
//Some code that might throw an exception
}
catch(Exception ex)
{
LogManager.Log(ex.ToString());
}
You should never use exceptions to return information from a method or execute business rules in your application. It is a bad design to use exception handling to return information from a method. If you are returning exception data from your method, then your class design is wrong and should be revisited.
Finally, you should throw exceptions only when necessary, and avoid them whenever possible. You can avoid exceptions in your code by using proper validation logic. You can refer to my article on avoiding exceptions in C# and to Microsoft’s documentation for more information.