Exception Management in .NET

Exceptions are a very powerful concept when used correctly. An important cornerstone in the design of a good application is the strategy you adopt for Exception Management. You must ensure that your design is extensible to be able to handle unforeseen exceptions gracefully, log them appropriately and generate metrics to allow the applications to be monitored externally.

(Note: This article won the OSNews Aspire competition a few weeks ago.)


If you come are a COM background, you would know that there is not one way to generate and handle exceptions, which to me as a consumer and creator of components was a very painful task to handle. Just the different ways to deal with exceptions between c++ and VB would be enough to drive a sane person up the wall. Thankfully, in .NET provides a better approach to resolve the whole mess. The .NET Common Language Runtime (CLR) provides a uniform way to notify the different applications of an exception, even if they are written in different languages.

Understanding Exceptions

Exception Classes Hierarchy Most programmers who I have worked with that are either new to programming or do not come from an object oriented background (such as c++. Java, etc.) have a poor understanding of what an exception is. An exception is the violation of a contained assumption. E.g. your code tried to access a file that it assumes to be present, but if is not then an exception would be thrown. However if you application first checked to see if the file was present before trying to access it, then that scenario may not throw an exception.

It is important to understand is that an exception is not necessarily an error. The context in which an exception occurs within the application governs if it is an error or not.

All Exception classes should derive from the Exception base class. The exceptions can be created either by the runtime or programmatically by your application. All exceptions generated by the runtime are derived from the SystemException class (which ultimately derive from the Exception class). Your application’s custom exceptions should derive from the ApplicationException class which also derives from the Exception base class.

try<br>
{<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//try and do something here...<br>
}<br>
catch (SomeExeption ex) <br>
{<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//Handle the exception and take appropriate action<br>
}<br>
finally<br>
{<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//This code will *always* run irrespective if you have<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//an exception or not. Usually this is some clean up of<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//some sort though.<br>
}<br>

The try block has the code that could throw an exception and the catch block catches it. You can have more than one catch block and should be ordered from the most specific to the most generic in ascending order. The first catch filter that matches is the one that “catches” the exception, so it is important to keep the order in mind.

Exceptions vs. Errors

Exceptions are very expensive operation and should not be taken lightly (as most developers do). As a developer you need to understand the distinction between an exception and a error and when should you use one over the other. In general, you should throw an exception only when it violates some condition outside of your code.

Take the example where in your application you have an Update Profile screen (or page), where the user can update their profile with the user’s First and Last Name as required fields. In this scenario, throwing an exception when the user forgets to enter one of the required fields would not be a good practice, as this should be an expected condition for the application and should be handled via the normal flow of execution. However, if the database that the application connects with, to update the profile is not available is an Exception. Many developers get lazy here and treat everything as an exception.

The following example uses a if statement to check whether a connection is closed. You can use this method instead of throwing an exception if the connection is not closed.

if(conn.State != ConnectionState.Closed)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;conn.Close();<br>


In the following example, an exception is thrown if the connection is not closed.
try {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;conn.Close();<br>
}<br>
catch(InvalidOperationException ex) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//Do something with the error or ignore it.<br>
}<br>

The method you choose depends on how often you expect the event to occur. If the event is truly exceptional and is an error (such as an unexpected end-of-file), using exception handling is better because less code is executed in the normal case. If the event happens routinely, using the programmatic method to check for errors is better. In this case, if an exception occurs, the exception will take longer to handle.

Exception Propagation

There are typically three ways, on how to propagate an exception up the chain:

  • Propagate Automatically – here you do not do anything and deliberately ignore the exception. This would cause the control to leave the current code block and move up the stack until an appropriate catch filter is found. If none is found, ultimately the user would see the exception in all its glory (which almost always is not a good idea).
  • Catch and Rethrow – here you typically try to recover from the exception if possible or do some clean up before propagating it up the stack.
  • Catch, Wrap and Rethrow – As an exception moves up the stack, it becomes less relevant. You can wrap the original exception into another one, which is more relevant to the caller.
  • Designing your Exception Class

    First and foremost, if an exception class already exists in the framework, use that instead of creating your own. You should typically only create a new exception for conditions that your code would need to handle and it is not already available either in the framework or an existing application exception hierarchy. A few things to keep in perspective when designing your application hierarchy:

  • The grouping of the exceptions should make sense to the caller of your code.
  • Keep it as flat as possible.
  • Store your hierarchy in one assembly if possible that can be referenced by your application (and its various components). This also helps centralize the management and deployment of your code.
  • Always end your custom exception class with the literal “Exception”.
  • Always provide the three constructors (shown in the example below), in your exception class.
  • Create a base exception class for your application that derives from ApplicationException and all other exceptions derive from that base exception.
  • Include a localized description string in every exception. When the user sees an error message, it is derived from the description string of the exception that was thrown, rather than from the exception class.
  • Use grammatically correct error messages, including ending punctuation.
  • Provide Exception properties for programmatic access. Also include extra information in an exception (in addition to the description string) only when there is a programmatic scenario where the additional information is useful.
  • Return null for extremely common error cases. For example, File.Open returns null if the file is not found, but throws an exception if the file is locked.
  • Throw an InvalidOperationException if a property set or method call is not appropriate given the object’s current state.
  • Throw an ArgumentException or a class derived from ArgumentException if bad parameters are passed.
  • The stack trace begins at the statement where the exception is thrown and ends at the catch statement that catches the exception. Be aware of this fact when deciding where to place a throw statement.
  • Use exception builder methods. It is common for a class to throw the same exception from different places in its implementation. To avoid excessive code, use helper methods that create the exception and return it.
  • Clean up intermediate results when throwing an exception as the callers should be able assume that there are no side effects of the exception when thrown.
  • For user-defined exceptions, you must ensure that the metadata for the exceptions is available to code executing remotely, including when exceptions occur across application domains. If you want to be able to remote custom exceptions then use the [Serializable] attribute and add the following constructor:
    protected YourBaseApplicationException(SerializationInfo info,StreamingContext context) : base(info, context)
  • Sample Base Application Exception:

    public class YourBaseApplicationException : ApplicationException<br>
    {<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Default constructor<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public YourBaseApplicationException ()<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>
    <br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Constructor accepting a single string message<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public YourBaseApplicationException (string message) : base(message)<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>
    <br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Constructor accepting a string message and an <br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// inner exception that will be wrapped<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public YourBaseApplicationException(string message, <br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Exception inner) : base(message, inner)<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>
    }<br>

    Use Exceptions Wisely

    Like I said earlier, exceptions are very expensive and it is best to use them wisely. There is a fine line in using them the proper way versus the “bad” way. A good example of what not to do which I come across very often is below.

    public IDbConnection Open()
    {<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Connection = new OracleConnection(this.ODSWebConnectionString);<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Connection.Open();<br>
    <br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return Connection;<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch(Exception exp)<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw exp;<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>
    }

    In the example above, the code catches and exception just to rethrow it which adds a lot of overhead for something which does not do anything. In the above example, you should not have the try…catch block as it does not add anything else. If you want to understand all the steps involved (and why this is an expensive operation) refer to the Appendix.

    Best Practices for “Consuming” Exceptions.

    A well-designed set of error handling code blocks can make a program more robust and less prone to crashing because the application handles such errors. The following list contains suggestions on best practices for handling exceptions:

  • Know when to set up a try/catch block. For example, you can programmatically check for a condition that is likely to occur without using exception handling. In other situations, using exception handling to catch an error condition is appropriate.
  • Use try/finally blocks around code that can potentially generate an exception and centralize your catch statements in one location. In this way, the try statement generates the exception, the finally statement closes or deallocates resources, and the catch statement handles the exception from a central location.
  • Always order exceptions in catch blocks from the most specific to the least specific. This technique handles the specific exception before it is passed to a more general catch block.

  • I would highly recommend using the Exception Application Block that is provided by Microsoft. The Exception Application Block provides a simple and extensible framework for logging exception information to the Event Log. You can also create your own components, to log exception details to other data sources or to notify operators, without affecting your application code.

    This can easily be used as a building block in your own application. If you use it, you will reduce the amount of custom error handling code you need to create, test, and maintain. You will also make your application more robust and easier to debug. Specifically, the Exception Management Application Block will help you:

  • Manage exceptions in an efficient and consistent way
  • Isolate exception management code from business logic code
  • Handle and log exceptions with a minimal amount of custom code

  • For more information and references please refer to the appendix.


    Appendix

    Please note that all the information in the appendix is from MSDN and .NET SDK. It is the copyright of their respective owners.

    The following is a step-by-step description of how a first chance exception is handled:

  • The runtime informs the debugger that a first chance exception has occurred. The Debugger Interface calls Exception on the ICorDebugManagedCallback interface that the debugger registered with the runtime.
  • The debugger obtains information about the exception. The debugger calls GetCurrentException on the ICorDebugThread object it was passed in the callback to obtain an exception object (ICorDebugValue).
  • The debugger obtains the ICorDebugObjectValue object for the exception. The debugger calls QueryInterface on the ICorDebugValue object to obtain the ICorDebugObjectValue object for the exception.
  • The debugger obtains the class of the exception object that was thrown. The debugger calls GetClass on the ICorDebugObjectValue exception object.
  • The debugger decides to ignore the exception by simply continuing.
  • The runtime informs the debugger that a last chance exception has occurred. The Debugger Interface component calls Exception on the ICorDebugManagedCallback object and specifies that the exception is a “last chance” exception.
  • The user decides the exception is inconsequential. The debugger calls ClearCurrentException on the ICorDebugThread object for the current debuggee thread. This clears the exception and prevents the exception from being thrown.
  • The debugger continues execution of the process. The debugger calls Continue on the ICorDebugProcess object for the current debuggee process.

  • Exception callback sequence

    Microsoft has an excellent paper entitled Exception Management Architecture Guide.


    About the Author
    Amit Bahree is a Solution Architect in Cap Gemini Ernst and Young’s Advanced Development and Integration practice. He specializes in Microsoft technologies and platforms and can be reached at [email protected]. His blog can be found at http://www.desigeek.com


    If you would like to see your thoughts or experiences with technology published, please consider writing an article for OSNews.

    25 Comments

    1. 2004-04-27 8:43 am
    2. 2004-04-27 9:37 am
    3. 2004-04-27 10:09 am
    4. 2004-04-27 10:26 am
    5. 2004-04-27 10:38 am
    6. 2004-04-27 12:15 pm
    7. 2004-04-27 12:36 pm
    8. 2004-04-27 12:42 pm
    9. 2004-04-27 1:09 pm
    10. 2004-04-27 2:46 pm
    11. 2004-04-27 2:53 pm
    12. 2004-04-27 3:16 pm
    13. 2004-04-27 4:17 pm
    14. 2004-04-27 4:20 pm
    15. 2004-04-27 4:51 pm
    16. 2004-04-27 5:25 pm
    17. 2004-04-27 5:32 pm
    18. 2004-04-27 5:38 pm
    19. 2004-04-27 6:08 pm
    20. 2004-04-28 4:51 am
    21. 2004-04-28 1:14 pm
    22. 2004-04-28 1:36 pm
    23. 2004-04-28 2:24 pm
    24. 2004-04-28 7:42 pm
    25. 2004-04-29 12:42 am