The following guidelines help ensure that your library handles exceptions appropriately.
You can catch exceptions when the purpose of catching the exception is to re-throw or transfer the exception to a different thread. The following code example demonstrates incorrect exception handling.
public class BadExceptionHandlingExample1 { public void DoWork() { // Do some work that might throw exceptions. } public void MethodWithBadHandler() { try { DoWork(); } catch (Exception e) { // Swallow the exception and continue // executing. } } }
An application should not handle exceptions that can result in an unexpected or exploitable state. If you cannot predict all possible causes of an exception and ensure that malicious code cannot exploit the resulting application state, you should allow the application to terminate instead of handling the exception.
Instead of creating lists of special exceptions in your catch clauses, you should catch only those exceptions that you can legitimately handle. Exceptions that you cannot handle should not be treated as special cases special-cased in non-specific exception handlers. The following code example demonstrates incorrectly testing for special exceptions for the purposes of re-throwing them.
public class BadExceptionHandlingExample2 { public void DoWork() { // Do some work that might throw exceptions. } public void MethodWithBadHandler() { try { DoWork(); } catch (Exception e) { if (e is StackOverflowException || e is OutOfMemoryException) throw; // Handle exception and continue // executing. } } }
You should catch only those exceptions that you can recover from. For example, a FileNotFoundException that results from an attempt to open a non-existent file can be handled by an application because it can communicate the problem to the user and allow the user to specify a different file name or create the file. A request to open a file that generates an ExecutionEngineException should not be handled because the underlying cause of the exception cannot be known with any degree of certainty, and the application cannot ensure that it is safe to continue executing.
Catching exceptions that you cannot legitimately handle hides critical debugging information.
The purpose of a catch clause is to allow you to handle exceptions (for example, by logging a non-fatal error). The purpose of a finally clause is to allow you to execute cleanup code regardless of whether an exception was thrown. If you allocate expensive or limited resources such as database connections or streams, you should put the code to release them inside a finally block.
The following code example shows a method that can throw an exception. This method is referenced in later examples.
public void DoWork(Object anObject) { // Do some work that might throw exceptions. if (anObject == null) { throw new ArgumentNullException("anObject", "Specify a non-null argument."); } // Do work with o. }
The following code example demonstrates catching an exception and incorrectly specifying it when re-throwing the exception. This causes the stack trace to point to the re-throw as the error location, instead of pointing to the DoWork method.
public void MethodWithBadCatch(Object anObject) { try { DoWork(anObject); } catch (ArgumentNullException e) { System.Diagnostics.Debug.Write(e.Message); // This is wrong. throw e; // Should be this: // throw; } }
The .NET Framework version 2.0 wraps non-CLS-compliant exceptions in a class derived from Exception.
Portions Copyright 2005 Microsoft Corporation. All rights reserved.
Portions Copyright Addison-Wesley Corporation. All rights reserved.
For more information on design guidelines, see the "Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries" book by Krzysztof Cwalina and Brad Abrams, published by Addison-Wesley, 2005.