Geeks With Blogs

News



Microsoft Store

Support This Site


AddThis Social Bookmark Button

Locations of visitors to this page

Subscribers to this feed

TwitterCounter for @sdorman

Creative Commons License


Scott Dorman Microsoft MVP, Software Architect, Developer, Author

It seems there is a lot of confusion on how to properly catch exceptions in .NET, especially among newer developers. There are a lot of good references available on MSDN and on the web, but a lot of these are either very advanced or just hard to follow.

I'm not going to discuss the CLR exception classes and go into the internals of how the .NET runtime generates exceptions. I don't want to discount this information, as it is helpful to know, but it isn't essential to understanding how to properly handle exceptions.

The most important thing to understand when handling exceptions is the concept of the Try/Catch/Finally block. This is a special section of code that is designed to allow you to work with exceptions. (This is a bit of a simplification, but that's the general idea.) Almost any line of code can cause an exception, but most applications don't actually need to deal with these exceptions. You should only handle an exception if there is something meaningful you can do as a result.

There are three ways you can specify a Try/Catch/Finally block:

  1. Try/Catch
  2. Try/Finally
  3. Try/Catch/Finally

In order to understand the differences, it is important to first understand what the different sections are responsible for.

  • The Try block contains sections of code that might throw an exception.
  • The Catch block contains the code that should run when an exception is encountered.
  • The Finally block contains code that should always run, even if an exception was encountered.

The syntax for a Try/Catch/Finally block looks like this:

   1: int[] array1={0,0};
   2: int[] array2={0,0};
   3:  
   4: try
   5: {
   6:     Array.Copy(array1,array2,-1);
   7: }
   8: catch (ArgumentOutOfRangeException e)
   9: {
  10:     Console.WriteLine("Error: {0}", e);
  11: }
  12: finally
  13: {
  14:     Console.WriteLine("This statement is always executed.");
  15: }

The part that tends to cause problems is the Catch handlers.

When an exception occurs, each catch block in the calling stack is given the opportunity to handle it. The proper catch block is determined by matching the type of the exception to the type of exception specified in the catch block.

A catch handler that only catches System.Exception or System.SystemException is called a general catch handler and should be avoided whenever possible as it can hide run-time problems and complicate debugging.

Since all exceptions in .NET inherit from System.Exception, this type of catch block will always match for any exception. If you think of catch blocks being evaluated in a "top down" approach, the first catch block that matches will be the one used. This means that if you put a general catch block first, none of the other catch blocks will be evaluated.

   1: try
   2: {
   3:     //some operation that results in InvalidCastException
   4: }
   5: catch (InvalidCastException e)
   6: {
   7:     Console.WriteLine("Invalid cast occurred.");
   8: }
   9: catch (Exception e)
  10: {
  11:     Console.WriteLine("General catch handler.");
  12: }

In the example above, the output would be "Invalid cast occurred." However, if the catch blocks were reversed, the output would be "General catch handler."

Another common mistake is to use an empty catch handler. This is a catch handler that doesn't  specify any exceptions and just uses the Catch keyword and is a result of legacy programming habits from the .NET Framework 1.0 and 1.1 releases.

In the .NET Framework 1.0 and 1.1 releases, there were situations where unmanaged code would throw an exception that wasn't properly handled by the runtime. As a result, it wasn't wrapped in a System.Exception derived exception and couldn't be caught by anything other than an empty catch block. This issue was corrected in .NET 2.0 and now these exceptions are wrapped in a System.Runtime.CompilerServices.RuntimeWrappedException (which inherits from System.Exception), so there is no longer a need for this empty catch block.

It is also common for people to write catch blocks that do nothing more than log the error. While this is sometimes important, it can usually be done by the top level caller and does not need to performed for each and every function. The best way to look at this situation is that you should only catch an exception if you have meaningful cleanup work that needs to be done as a result of the exception (like closing files or database connections).

This leads to a problem known as "swallowing exceptions", and occurs when you catch an exception and either do nothing with it or don't allow it to pass up the chain. This can also lead to problems because you are effectively hiding the exception and not doing anything with it, which can lead to intermittent problems that will be very hard to track down.

While we're talking about exception swallowing, there is a similar problem known as "breaking the stack". I talk about this in detail in another post, but it boils down to the fact that when you rethrow an exception you should almost always use the "throw" syntax.

Finally, if you are using a class that implements the System.IDisposable interface you can make use of the using Statement (C#) to simplify the code you need to write. I talk more about the using statement in this post.

References

Posted on Wednesday, September 26, 2007 5:42 PM .NET (General) , .NET (C#) | Back to top


Comments on this post: Catching (Handling) Exceptions in .NET

# re: Catching (Handling) Exceptions in .NET
Requesting Gravatar...
Scott, what's your take on Microsoft's recommended practice of "catch only the errors you know about and can recover from"?

MS recommends we don't handle exceptions unless we know we can recover from the exception. In other words, if the Error property propagated back up to us is an ArgumentException, great, we can probably handle it. But if it was an OutOfMemoryException or some other nasty, we shouldn't handle it and we should throw the exception immediately. Makes sense.

The problem gets bad, though: imagine some non-trivial code that does interesting things: maybe it's doing some .NET remoting that fails -- all kinds of bad things can happen there! -- , or maybe it hits some methods on our server that fails -- all kinds of things can happen there! -- maybe the method call chain is 20 methods deep - no way in hell we can write code on the client that knows what exceptions any of the 20 methods in the call chain will throw. No way.

And yet, those exceptions that could happen due to remoting or server errors should *NOT* cause the client to fail fast. And yet, because we cannot possibly keep track of all the possible exceptions, we either have to fail fast in the face of any Exception, or handle all Exceptions and pray it's nothing nasty.

Even if we could keep track of all possible exceptions, if there's a slight change in the call stack, it would affect all upstream callers, and we'd have to change all the error handling for those upstream callers. Ouch.

Again, no way; that's not realistic.

MS's exception handling best practices, which can be summed up as "only handle exceptions you know about, otherwise throw and fail fast", is not rooted in reality. It's a theoretical best practice that cannot be carried out over a medium sized projects or bigger.

What do you think?
Left by Judah Himango on Sep 27, 2007 11:48 PM

# re: Catching (Handling) Exceptions in .NET
Requesting Gravatar...
Overall, I think Microsoft's recommended best practice of "catch only the errors you know about and can recover from" is very good guidance. As you provided in your example, there are certain errors like ArgumentException that you can handle but others like OutOfMemoryException that you can't.

I understand the concern you are relating when it comes to handling exceptions across a remoting layer, but I don't think it is as bad as you describe. While it is true that there are numerous places this type of call can fail, they all boil down to two locations: the client or the server.

Your scenario very clearly highlights the need for a well thought out architecture around the remoting calls. By doing this, the server side of the call would handle any errors that it might encounter that it can do something about and take the appropriate steps to clean up. It's at this point that the determination should be made as to whether or not the client cares about the error and, if necessary, wrap it in a custom exception type that makes sense to the client.

It is impossible for the client (and for that matter, even the server) to keep track of all possible exceptions. All of the Microsoft provided exception classes are serializable so they will transfer over the remoting channel back to the client, who can then decide what to do. While your example of the call chain on the server being 20 methods deep is valid, in the vast majority of cases the client isn't going to care about 99% of the errors that could occur somewhere deep in that call chain. The server will, and will take the appropriate steps based on those exceptions and inform the client as needed.

I do think this best practice is realistic and not purely a theoretical best practice. I have used this concept in large enterprise scale applications that use remoting with a great deal of success. It does, however, make some rather strong assumptions about how the code is architected, which can have a significant impact on how you handle exceptions and attempt to recover from them.
Left by Scott on Sep 28, 2007 6:07 PM

# re: Catching (Handling) Exceptions in .NET
Requesting Gravatar...
I think .NET as a language needs something stronger that XML comments to tell the consumer what exceptions to expect.

I think back to Java's checked exceptions, but there are problems with that which make me think twice.

We could also use some static analysis tools to tell us which exceptions can be thrown and under what circumstances.

I still think it's unrealistic -- or at the very least, utterly complex, time-consuming, and fragile -- to say we need to know about, document, and keep up-to-date a list of exceptions every function in the app throws. That's just not realistic IMO. It's too fragile, it relies solely on the good-will of the author, it's not compiler-enforced, and it's near impossible to maintain in the face of refactorings.

p.s. I apologize if this was posted multiple times; the comment system kept spitting it out.
Left by Judah Himango on Oct 01, 2007 3:20 PM

Your comment:
 (will show your gravatar)


Copyright © Scott Dorman | Powered by: GeeksWithBlogs.net | Join free