Exceptions in Components

Summary

Best Practices for Throwing Exceptions from Components

When designing components, an error handling strategy must be thought-out and implemented consistently. There are a number of programming trade-offs to consider but the most important should be the convenience for the developer using your component. The general strategy is:

If it is possible for the component to handle exceptions internally, it should. Throwing exceptions from a component to its client(s) is the appropriate behavior when additional intervention is required.

When an application calls a method on your component, the method should provide error information by throwing an exception. It is then the responsibility of the client application to implement an exception handler to trap, analyze, re-try if possible, and finally display an error message. Here are some of the general guidelines for throwing and handling exceptions in components:

Your component should integrate with its client application in a completely transparent manner

When a client program calls a method on your component, any thrown exceptions whether by the component or by the .NET Framework that are not handled will be passed on to the client. A well-behaved component does not intrude on the client application's UI by displaying popup message boxes containing error text. This is because users of the component's client are most-of-the time unaware that your component is part of the application they are using.

Be consistent

Exceptions thrown from your component should be logically consistent:

COM-Visible Components

There are additional considerations to take into account if your component will be used by a COM client. All exceptions thrown from a .NET component to a COM client must provide an error code. With standard exceptions, the error code is provided for you behind the scenes via the HResult property of the exception object. With custom exceptions, you must set an appropriate HResult value. In this case, make sure to properly document those error codes to help developers implement appropriate responses.

Throwing Exceptions from Components

An Exception that occurs in your component but is not handled within, will automatically be thrown to the client application until it either finds an appropriate error handler or the CLR default error handler handles it. The general strategy for handling exceptions in a component would be:

try
{
    // Code that can throw exceptions
}
catch(OverflowException eInternal)
{
    throw new OverflowException( "The business processing layer failed to process order due to an overflow error", eInternal )
}
catch(NullReferenceException eInternal)
{
    throw new OverflowException( "The business processing layer failed to process order due to an invalid account", eInternal )
}

Best Practices for Handling Exceptions in Components

When writing a component you should be prepared to handle different kinds of exceptions:

Exceptions generated by your component that are handled internally

The component attempts to handle these exceptions internally, and if successful, they are not passed back to the client. For example, your component may attempt to open a file with a set of restrictive read access parameters. If it fails, an exception is thrown and the process is retries until the file can be opened.

Exceptions generated by your component that passed to the calling client

The component attempts to handle these exceptions internally, and if unsuccessful, they are passed back to the client. In this case, it makes sense to provide additional information back to the client.

Exceptions generated by another component

If your component calls methods from other components, then your component must be ready to handle exceptions from these other components. These other components should have documentation explaining what exceptions are thrown and under what circumstances. It is quite important that your component handles exceptions from other components and not return these exceptions to the client. The client was written to call your component and to handle your component exceptions and not exceptions from other components. It must be assumed that the client calling your component has no idea of the constituent components you are using.

Encapsulation

The idea that a client application should handle exceptions only from components it directly calls is related to the object-oriented concept of encapsulation. The client is completely unaware how you use other components. A well-written component will encapsulate all other components it uses including handling exceptions thrown by these components.

In some cases however, it might make sense for your component to report exceptions from encapsulated components to the client application. This is typically in situations where additional input or intervention is required by the client. In these scenarios, do not allow the original exception to bubble up to the client, but rather handle it using of the choice below:

For example::

try
{
    SomeEncapsulatedComponent.foo();
}
catch( OverflowException eOriginal)
{
    throw new OverflowException( "Some meaningful comment within the context of the affected operation", eOriginal );
}

Handling Exceptions in Components

Your should anticipate potential problems and write your exception code to handle these problems. If an exception cannot be successfully handled, determine what code must be run (usually clean-up code in the finally block) before passing on the exception one-level up to the client. For example:

try
{
    // Anticipate exceptions when calling any method
    SomeEncapsulatedComponent.foo();
}
catch( OverflowException eOriginal)
{
    throw new OverflowException( "Some meaningful comment within the context of the affected operation", eOriginal );
}
catch( ArgumentOutOfRangeException eOriginal )
{
    throw new ArgumentOutOfRangeException ( "Some meaningful comment within the context of the affected operation", eOriginal );
}
catch( Exception eOriginal )
{
    throw new Exception ( "Some meaningful comment within the context of the affected operation", eOriginal );
}
finally
{
    // Clean-up code comes here
}