(I would be constantly updating this post… to include as much information as I can on .NET exception handling and best practices associated with it. )
It is well known that, throwing excessive exceptions decreases the performance of the application. Yet, 4 out of 10 .NET applications continue to suffer from exception related performance problems. In this blog post, I would like to compile all the guidelines related to exception handling and also list out how to do troubleshooting the performance issues related to .NET exceptions.
Microsoft Recommended Guidelines on Exception Management:
- Do not return error codes. Exceptions are the primary means of reporting errors in frameworks.
- Do report execution failures by throwing exceptions. If a member cannot successfully do what it is designed to do, that should be considered an execution failure and an exception should be thrown.
- Consider terminating the process by calling System.Environment.FailFast(System.String) (a .NET Framework version 2.0 feature) instead of throwing an exception, if your code encounters a situation where it is unsafe to continue executing.
- Do not use exceptions for normal flow of control, if possible. Except for system failures and operations with potential race conditions, framework designers should design APIs so that users can write code that does not throw exceptions. For example, you can provide a way to check preconditions before calling a member so that users can write code that does not throw exceptions.
- Consider the performance implications of throwing exceptions.
- Do document all exceptions thrown by publicly callable members because of a violation of the member contract (rather than a system failure) and treat them as part of your contract. Exceptions that are a part of the contract should not change from one version to the next.
- Do not have public members that can either throw or not throw exceptions based on some option.
- Do not have public members that return exceptions as the return value or as an out parameter.
- Consider using exception builder methods. It is common to throw the same exception from different places. To avoid code bloat, use helper methods that create exceptions and initialize their properties.
- Do not throw exceptions from exception filter blocks. When an exception filter raises an exception, the exception is caught by the common language runtime (CLR), and the filter returns false. This behavior is indistinguishable from the filter executing and returning false explicitly and is therefore very difficult to debug.
- Avoid explicitly throwing exceptions from finally blocks. Implicitly thrown exceptions resulting from calling methods that throw are acceptable.
Microsoft Recommended Best Practices for Exception Handling:
- 1. Do not handle errors by catching non-specific exceptions, such as System.Exception, System.SystemException, and so on, in framework code.
- Avoid handling errors by catching non-specific exceptions, such as System.Exception, System.SystemException, and so on, in application code. There are cases when handling errors in applications is acceptable, but such cases are rare.
- Do not exclude any special exceptions when catching for the purpose of transferring exceptions.
- Consider catching specific exceptions when you understand why it will be thrown in a given context.
- Do not overuse catch. Exceptions should often be allowed to propagate up the call stack.
- Do use try-finally and avoid using try-catch for cleanup code. In well-written exception code, try-finally is far more common than try-catch.
- Do prefer using an empty throw when catching and re-throwing an exception. This is the best way to preserve the exception call stack.
- Do not handle non-CLS-compliant exceptions (exceptions that do not derive from System.Exception) using a parameter-less catch block. Languages that support exceptions that are not derived from Exception are free to handle these non-CLS compliant exceptions.
Microsoft Recommended Best Practices for improving performance
- Do not use error codes because of concerns that exceptions might affect performance negatively.
- Consider the Tester-Doer pattern for members that may throw exceptions in common scenarios to avoid performance problems related to exceptions.
- Consider the TryParse pattern for members that may throw exceptions in common scenarios to avoid performance problems related to exceptions.
- Do provide an exception-throwing member for each member using the TryParse pattern.
Microsoft Recommended Guidelines for defining Custom Exceptions
- Avoid deep exception hierarchies.
- Do derive exceptions from System.Exception or one of the other common base exceptions.
- Do end exception class names with the Exception suffix.
- Do make exceptions serializable. An exception must be serializable to work correctly across application domain and remoting boundaries.
- Do provide (at least) the following common constructors on all exceptions. Make sure the names and types of the parameters are the same those used in the following code example.
- Do report security-sensitive information through an override of System.Object.ToString only after demanding an appropriate permission. If the permission demand fails, return a string that does not include the security-sensitive information.
- Do store useful security-sensitive information in private exception state. Ensure that only trusted code can get the information.
- Consider providing exception properties for programmatic access to extra information (besides the message string) relevant to the exception.
Use the class or subclass of the exceptions listed below if possible.
Perfmon counters to identify .NET Exceptions
# of Exceps Thrown
This counter displays the total number of exceptions thrown since the start of the application. These include both .NET exceptions and unmanaged exceptions that get converted into .NET exceptions e.g. null pointer reference exception in unmanaged code would get re-thrown in managed code as a .NET System.NullReferenceException; this counter includes both handled and unhandled exceptions. Exceptions that are re-thrown would get counted again. Exceptions should only occur in rare situations and not in the normal control flow of the program.
# of Exceps Thrown /sec
This counter displays the number of exceptions thrown per second. These include both .NET exceptions and unmanaged exceptions that get converted into .NET exceptions e.g. null pointer reference exception in unmanaged code would get re-thrown in managed code as a .NET System.NullReferenceException; this counter includes both handled and unhandled exceptions. Exceptions should only occur in rare situations and not in the normal control flow of the program; this counter was designed as an indicator of potential performance problems due to large (>100s) rate of exceptions thrown. This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval.
# of Filters / sec
This counter displays the number of .NET exception filters executed per second. An exception filter evaluates whether an exception should be handled or not. This counter tracks the rate of exception filters evaluated; irrespective of whether the exception was handled or not. This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval.
# of Finallys /sec
This counter displays the number of finally blocks executed per second. A finally block is guaranteed to be executed regardless of how the try block was exited. Only the finally blocks that are executed for an exception are counted; finally blocks on normal code paths are not counted by this counter. This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval.
Throw To Catch Depth / sec
This counter displays the number of stack frames traversed from the frame that threw the .NET exception to the frame that handled the exception per second. This counter resets to 0 when an exception handler is entered; so nested exceptions would show the handler to handler stack depth. This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval.