Exceptional conditions about exceptions
A number of subtle points about exception processing.

This is one of those lengthy discussions that get filed under “There are always exceptions”. And, it happens to be related to the guidance I wrote on exceptions in Effective C#.

First, the simple question:

“Recently, I and fellow developers have a very heated and passionate debate on whether or not it is appropriate to derive one's application-specific exception from System.ApplicationException or System.Exception. (I was the lone voice, which now advocates the latter despite my past recommendation).
So far Microsoft and all its documentation recommend what you stated in the book and I have been following this recommendation. But of late, particularly in Whidbey Beta 1, the recommendation has changed. Even FxCop 1.312 for Whidbey has changed.
It now recommends the application-specific exception *should* derive from System.Exception instead.
I was initially furious with FxCop but after doing research, particularly SLAR (.Net Framework Standard Library Annotated Reference) and this URL go further to suggest the BCL team has admission of mistake: http://blogs.msdn.com/brada/archive/2004/03/25/96251.aspx

Well, yeah, we all goofed. It turns out that creating an application specific base class for exceptions doesn’t buy anything. In fact, it only makes writing good catch clauses harder. As I pointed out in Item 44, the main reason to create new exception clauses is to facilitate catching different errors and responding to them with different actions. Knowing that an exception was generated in application code vs. library code doesn’t really play into that decision. In fact, all it does is introduce an extra level of complexity in the class hierarchy. The world is a simpler place without using ApplicationException.

And now, the subtle correct behavior in Item 44:

“Also because Exception is derived from ISerializable, I don't seem to recall seeing the mentioning of ISerializable.GetObjectData(), which is mentioned in Item 25).”

Correct, I did not add a GetObjectData() method in any of the examples in Item 44. The reason is simple: None of these classes add any data fields not already found in the base class, System.Exception. For that reason the correct implementation of any derived GetObjectData would do nothing except call the base class, providing no extra functionality. I should have explained my reason for not including it, but the samples are correct without it.

And finally, the tough one:

“In relation to the use of Inner Exception, I have two minds about it. One is if you provide a form of abstraction where the client of your class/assembly needs not be aware of the implementation details. If the class designer wisely implements an Application-specific exception in the public interface (which exceptions are part of it) to support exception translation.

If the class designer unwittingly uses inner exception to embed the exception thrown by the implementation assembly, then this forces the client also to reference the implementation assembly in order to deserialize the application-specific exception. This to me is a break in abstraction. The problem is particularly acute in cross app domain calls, such as remoting. For instance class that support remoting service from a Data Access Layer. The client should not need to include say SqlClient in order to deserialize an exception thrown from the DAL.

The other area where the embedded inner exception can hurt is in those assembly that are loaded dynamically via Assembly.Load() using configuration details, generally in their own app domain. When the exception is deserialized it would also need to assembly supporting the inner exception to be available on the receiving side. Failure to have them can result in a deserialization exception.

In this usage, I tend to recommend to my fellow developers to avoid embedding exception. But rather constructed an exception message that include enough details of the implementation detail for technical staff to perform diagnostic.”

This is the difficult question, because it touches on many edge cases. First, let’s cover the normal case: You should fill in the InnerException property, because it’s how you preserve as much information as possible about the original error. Failure to do so means that the technical staff loses that information.

Now let’s look at the two cases my reader mentioned. First, let’s cover remoting. Ideally, you’d like to preserve the InnerException property. But, it’s just not safe. As the reader points out, if the implementation of the runtime class used in the InnerException isn’t available on the client, the client gets a SerializationException. That effectively loses all information in your original exception, so it doesn’t help anyone.

You might be tempted to look at the runtime type of the exception and include it as the InnerException property if it is delivered with the framework, and therefore available on the client machine. That’s a mistake, because you would need to walk all the InnerException property of each inner exception to ensure that you never find a type that might not be delivered to the client.

The Assembly.Load() gives similar results but for different reasons. Once again, you don’t know what domain into which you code will be loaded. If an exception type (or any other type, for that matter), gets passed across the domain boundary, your client process must catch serialization exceptions.

The root cause in both cases is that an object is being passed across process boundaries. As an aside, you can create the same problems by returning a derived class where a base class was specified. If the derived class implementation is not available to the client process, it cannot be serialized, and the client must catch serialization exceptions.

So, what should you do? Well, this is one of the reasons I prefer an SOA model, or web services implementation, to communicate between processes. The Xml Serializer is more resilient in these kinds of situations. In fact, you can’t really throw an exception from a web service to the client application. Instead, you return an error document.

If you have chosen remoting and you’re planning to stick with it, you just have to remember that the app domain boundary is always a special case, and all the concrete types that are passed through that boundary must be available on both sides of the boundary. As the reader mentions, you need to use some other technique to preserve exception information. My own preference would be to log all information about the exception on the server, using something like the Exception Processing block in the Enterprise Library. After that’s done, you can create a new exception that references the logged information and sends that to the client for client side processing and reporting.

Inter-process (and app domain) boundaries are edge cases, and you need to take special care with information that crosses those boundaries, including exceptions.



Published Wednesday, June 08, 2005 11:11 PM by wwagner
Filed under:

Comments

No Comments

Leave a Comment

(required) 
(required) 
(optional)
(required)