Chapter 4. Exceptions

Exceptions are .NET’s primary mechanism for communicating error conditions. Exceptions have great power, but with great power comes great responsibility. Like anything, exceptions can be abused, but that is no excuse to underuse them.

Compared to returning error codes, exceptions offer numerous advantages, such as being able to jump up many frames in a call stack and including as much information as you want.

Throw an Exception

Solution: Use the throw syntax while creating the exception.

image

Catch an Exception

Solution: Wrap the code that could potentially throw an exception inside a try { } followed by a catch { }.

image

Catch Multiple Exceptions

Solution: You can have multiple catch blocks after a try. .NET will go to the first catch block that is polymorphically compatible with the thrown exception. If you have code that throws both ArgumentException and ArgumentNullException, for example, the order of your catch blocks is important because ArgumentNullException is a child class of ArgumentException.

Here’s an example of what not to do:

image

Because ArgumentNullException is a type of ArgumentException, and ArgumentException is first in the catch list, it will be called.

The rule is: Always order exception catch blocks in most-specific-first order.

image

Rethrow an Exception

Solution: There are two ways to do this, and the difference is important.

The naive (that is, usually wrong) way is this:

image

What’s so wrong with this? Whenever an exception is thrown, the current stack location is saved to the exception (see later in this chapter). When you rethrow like this, you replace the stack location that was originally in the exception with the one from this catch block—probably not what you wanted, and a stumbling block during debugging. If you want to rethrow while preserving the original stack trace call, throw without the exception variable.

image

Here’s the difference in a sample program:

image

You can see that the first stack trace has lost the original source of the problem (DoSomething), while the second has preserved it.

Note

Be smart about when to intercept an exception. For example, you don’t usually want to log an exception and then rethrow at multiple levels, causing the same error to be logged over and over again. Often, logging should be handled at the highest level.

Also, beware of the trap of habitually handling exceptions at too low of a level. If you can’t do something intelligent about it, just let a higher level worry about it.

(Almost) Guarantee Execution with finally

Solution: Use finally, which is guaranteed to run at the end of a try or a try-catch block. This happens whether you return, an exception is thrown, or execution continues normally to the line after the try-finally.

image

The output for this program is as follows:

image

Note that a catch block is not necessary when finally is used.

Note

Yes, finally is guaranteed to run—except when it’s not. If your code forces an immediate process exit, the finally block will not run.

image

Get Useful Information from an Exception

Solution: Exceptions are very rich objects—far more powerful than simple return codes. Table 4.1 lists the properties available to all exception types.

Table 4.1 Exception Properties

image

Here’s an example:

image

This program produces the following output:

image

This is when the program is run in Debug mode. Note the differences when the program is run in Release mode:

image

There is less information in the stack trace when a program is compiled in Release mode because the binary does not contain all the information necessary to generate it.

Create Your Own Exception Class

Solution: Your exception can contain whatever data you desire, but there are a few guidelines to follow. In particular, you should have certain standard constructors:

• No arguments (default constructor).

• Takes a message (what is returned by the Message property).

• Takes a message and an inner exception.

• Takes data specific to your exception.

• Takes serialization objects. Exceptions should always be serializable and should also override GetObjectData (from ISerializable).

Here is a sample exception that implements all of these recommendations:

image

image

Taking the time to implement exceptions as deeply as this ensures that they are flexible enough to be used in whatever circumstances they need to be.

Catch Unhandled Exceptions

Solution: A thrown exception is passed up the call stack until it finds a catch block that can handle it. If no handler is found, the process will be shut down.

Fortunately, there is a way to trap unhandled exceptions and execute your own code before the application terminates (or even prevent it from terminating). The way to do it depends on the type of application. The following are excerpts demonstrating the appropriate steps. See the sample code for this chapter for full working examples.

Note

When running these sample projects under Visual Studio, the debugger will catch the unhandled exceptions before your code does. Usually, you can just tell it to continue and let your code attempt to handle it.

Catch Unhandled Exceptions in the Console

In console programs, you can listen for the UnhandledException for the current AppDomain:

image

Catch Unhandled Exceptions in Windows Forms

In Windows Forms, before any other code runs, you must tell the Application object that you wish to handle the uncaught exceptions. Then, you can listen for a ThreadException on the main thread.

image

image

Catch Unhandled Exceptions in WPF Applications

In WPF, you listen for unhandled exceptions on the dispatcher:

image

Catch Unhandled Exceptions in ASP.NET Applications

In ASP.NET, you can trap unhandled exceptions at either the page level or the application level. To trap errors at the page level, look at the page’s Error event. To trap errors at the application level, you must have a global application class (often in Global.asax) and put your behavior in Application_Error, like this:

image

Usage Guidelines

Here are some general guidelines on exception handling:

• The .NET Framework uses exceptions extensively for error-handling and notification. So should you.

• Nevertheless, exceptions are for exceptional situations, not for controlling program flow. For a simple example, if an object can be null, check for null before using it, rather than relying on an exception being thrown. The same goes for division by zero, and many other simple error conditions.

• Another reason to reserve their use for exceptional error conditions is that they have performance cost, in both speed and memory usage.

• Pack your exceptions with as much useful information as you need to diagnose problems (with caveats given in the following bullets).

• Don’t show raw exceptions to users. They are for logging and for developers to use to fix the problem.

• Be careful about the information you reveal. Be aware that malicious users may use information from exceptions to gain an understanding about how the program works and any potential weaknesses.

• Don’t catch the root of all exceptions: System.Exception. This will swallow all errors, when you should be forced to see and fix them. It is okay to catch System.Exception, say for the purpose of logging, as long as you rethrow it.

• Wrap low-level exceptions in your own exceptions to hide implementation-level details. For example, if you have a collection class that is implemented internally using a List<T>, you may want to hide ArgumentOutOfRangeExceptions inside a MyComponentException.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset