Given that the .NET Framework includes a selection of predefined base class exception objects, how do you use them in your code to trap error conditions? To deal with possible error conditions in C# code, you will normally divide the relevant part of your program into blocks of three different types:
- try blocks encapsulate the code that forms part of the normal operation of your program and that might encounter some serious error conditions.
- catch blocks encapsulate the code that deals with the various error conditions that your code might have encountered by working ‘through any of the code in the accompanying try block. This place could a!so be used for logging errors.
- finally blocks encapsulate the code that cleans up any resources or takes any other action that you will normally want done at the end of a try or catch block. It is important to understand that the finally block is executed whether or not an exception is thrown, Because the. aim is that the finally block contains cleanup code that should always be executed, the compiler will flag an error if you place a return statement inside a finally block. For an example of using the finally block, you might close any connections that were opened in the try block.” It is all important to understand that the finally block is completely optional. If you do not have a requirement for any cleanup code (such as disposing or closing any open objects), then there is no need for this block.
So how do these blocks fit together to trap error conditions? Here is how:
- The execution flow first enters the try block.
- If no errors occur in the try block, execution proceeds normally through the block, and when the end of the try block is reached, the flow of execution jumps to the finally block if one is present (Step 5). However, if an error does occur within the try block, execution jumps to a catch block (Step 3).
- The error condition is handled in the catch block.
- At the end of the catch block, execution automatically transfers to the finally block if one is present.
- The finally block is executed (if present).
The C# syntax used to bring all of this about looks roughly like this:
Actually, a few variations on this theme exist:
- You can omit the finally block because it is optional.
- You can also supply as many catch blocks as you want to handle specific types of errors. However, the idea is not to get too carried away and have a huge number of catch blocks, because this can hurt the performance of your application.
- You can omit the catch blocks altogether, in which case the syntax serves not to identify exceptions, but as a way of guaranteeing that code in the finally block will be executed when execution leaves the try block. This is useful if the try block contains several exit posts.
So far so good, but the question that has yet to be answered is this: If the code is running in the try block, how does it know when to switch to the catch block if an error has occurred? If an error is detected, the code does something known as throwing an exception. In other words, it instantiates an exception object class and throws it:
Here, you have instantiated an exception object of the OverflowException class.As soon as the computer encounters a ehrowstatement inside a try block, it immediately looks for the catch block associated with that try block. If there is more than one catch block associated with the try block, it identifies the correct catch block by checking which exception class the catch block is associated with. For example, when the OverflowException object is thrown, execution jumps to the following catch block:
catch (OverflowException ex)
// exception handling here
In other words, the computer looks for the catch block that indicates a matching exception class instance of the same class (or of a base class).
With this extra information, you can expand the try block just demonstrated. Assume, for the sake of argument, that there are two possible serious errors that can occur in the try block: an overflow and an array out of bounds. Assume that your code contains two Boolean variables, .Overflow and OutOfBounds, which indicate whether these conditions exist. You have already seen that a predefined exception class exists to indicate overflow (OverflowException); similarly, an IndexOutOfRangeException class exists to handle an array that is out of bounds.
Now your try block looks like this:
So far;.this might not look that much different from what you could have done with the VisualBasic 6 On Error GoTo statement (with the exception perhaps that the different parts in the code are separated). Well, however, provides a far more powerful and flexible mechanism for error handling. This is because you can have throw statements that are nested in several method calls inside the try block, but the same try block continues to apply even as execution flow enters these other methods. If the computer encounters a throw statement, it immediately goes back up through all the method calls on the stack, looking for the end of the containing try block and the start of the appropriate catch block. During this process, all the local variables in the intermediate method calls will correctly go out of scope. This makes the try … catch architecture well suited to the situation described at the beginning of this section, where the error occurs inside a method call that is nested inside 15 or 20 method calls, and processing has to stop immediately.
As you can probably gather from this discussion, try blocks can play a very significant part in controlling the flow of execution of your code. However, it is important to understand that exceptions are intended for exceptional conditions, hence their name. You wouldn’t want to use them as a way of controlling when to exit a do … while loop.
ImplementIng MultIple Catch Blocks
The easiest way to see how try, ,.catch .,,finally blocks work in practice is with a couple of examples. The first example is called SimpleExceptions. It repeatedly asks the user to type in a number and then displays it. However, for the sake of this example, imagine that the number has to be between 0 and 5; otherwise, the program won’t be able to process the number properly. Therefore, you will throw an exception if the user types in anything outside of this range.
The program then continues to ask for more numbers for processing until the user simply presses the Enter key without entering anything.
You should note that this code does not provide a good example of when to use exception handling. As already indicated, the idea of exceptions is that they are provided for exceptional circumstances. Users are always typing in silly things, 50 this situation doesn’t really count. Normally, your program will. handle incorrect user input by performing an instant check and asking the user to retype the input if there is a problem. However, generating exceptional situations is difficult in a small example that you can read through in a few minutes! So, we will tolerate this bad practice for now in order to demonstrate how exceptions work. The examples that follow present more realistic situations.
The code for SimpleExceptions looks like this:
The core of this code is a while loop, which continually uses Console. ReadLine () to ask for user input. ReadLine () returns a string, so your first task is to convert it to an int using the System . Convert. Tolnt32 () method. The System. Convert class contains various useful methods to perform data conversions and provides an alternative to the int. Parse () method. In general, System. Convert contains methods to perform various type conversions. Recall that the C# compiler resolves int to instances of the system. Int32 base class.
It is also worth posting out that the parameter passed to the catch block is scoped to that catch block – which is why you are able to use the same parameter name, ex, in successive catch blocks in the preceding code. –
In the preceding example, you also check for an empty string, because this is your condition for exiting the while loop. Notice how the break statement actually breaks right out of the enclosing try block as well as the while loop because this is valid behavior. Of course, once execution breaks out of the try block, the Console. writeLine () statement in the finally block is executed. Although you just display a greeting here, more commonly, you will be doing tasks like closing file handles and calling the Dispose () method of various objects in order to perform any cleaning up. Once the computer leaves the finally block, it simply carries on executing unto the next statement that it would have executed had the finally block not been present. In the case of this example, though, you iterate back to the start of the while loop, and enter the try block once again (unless the finally block was entered as a result of executing the break statement in the while loop, in which case you simply exit the while loop).
Next, you check for your exception condition:
When throwing an exception, you need to choose what type of exception to throw. Although the class System. Exception is available, it is intended only as a base class. It is considered bad programming practice to throw an instance of this class as an exception, because it conveys no information about the nature of the error condition. Instead, the .NET Framework contains many other exception classes that are derived from System. Exception. Each of these matches a particular type of exception condition; and you are free to define your own ones as well. The idea is that you give as much information as possible about the particular exception condition by throwing an instance of a class that matches the particular error condition. In the preceding example, System. IndexOutOfRangeException is the best choke for the circumstances. IndexOutOfRangeException has several constructor overloads. The one. ch· sen in the example takes a string, which describes the error. Alternatively, you might choose to derive I your own custom Exception object that describes the error condition in the context of your application.
Suppose that the user then types a number that is not between 0 and 5. This will be picked up by the if statement and an IndexOutOfRangeException object will be instantiated and thrown. At this point, the computer will immediately exit the try block and hunt for a catch block that handles :i:ndexOutOfRangeException. The first catch block it encounters is this:
Because this catch block takes a parameter of the appropriate class, the catch block will be passed the exception instance and executed. In this case, you display an error message and the Exception. Message property (which corresponds to the string you passed to the IndexOutOfRangeException’s constructor).”After executing this catch block, control then switches to the finally block, just as if no exception had occurred.
Notice that in the example, you have also provided another catch block:
This catch block would also be capable of handling an IndexOutOfRangeException jf it were not for the fact that such exceptions will already have been caught by the previous catch block. A reference to a base class can also refer to any instances of classes derived from it, and all exceptions are derived from System. Exception. So why isn’t this catch block executed? The answer is that the computer executes only the first suitable catch block it finds from the list of available catch blocks. So why is this second catch block even here? Well, it is not only your code that is covered by the try block. Inside the block, you actually make three separate calls to methods in the System namespace (Console. ReadLine ( ), Console.Write (), and Convert. Tolnt32 (», and any of these methods might throw an exception.
If you type in something that is not a number – say a or hello – the Convert. Tolnt321) method will throw an exception of the class System. FormatException to indicate that the string passed into Tolnt32 () is not in a format that can be converted to an into When this happens, the computer will trace back through. the method calls, looking for a handler that can handle this exception. Your first catch block (the one that takes an IndexOutOfRangeException) will not do. The computer then looks at the second catch block. This one will do because FormatException is derived from Exception, so a FormatException instance can be passed in as a parameter here.
The structure of the example is actually fairly typical of a situation with multiple catch blocks. You start off with catch blocks that are designed to trap very specific error conditions. Then, you finish with more general blocks that will cover any errors-for which you have not written specific error handlers.Indeed, the order of the catch blocks is important. If you had written the previous two blocks in the opposite order, the code would not have compiled, because the second catch block is unreachable (the Exception catch block would catch all exceptions). Therefore, the uppermost catch blocks should be The most granular options available and ending with the most general options.
However, in the previous example, you have a third catch block listed in,the code:
This is the most general catch block of all- it does not take any parameter. The reason this catch block , is here is to catch exceptions thrown by other code that is not written in C# or is not even managed code at. ;all. You see, it is a requirement of the C, language that only instances of classes derived from System.Exception c# be thrown as exceptions, but other languages might not have this restriction – C++, for ,example, allows any variable whatsoever to be thrown as an exception. If your code calls into libraries or , .assemblies that have been written in other languages, it might find that an exception has been thrown that ~ not derived from System. Exception, although in many:cases, the .NET Invoke mechanism will trap :these’exceptions and convert them into .NET Exception objects. However, there is not that much that catch block can do, because you have no idea write class the exception might represent.
For this particular example, there is no point in adding this catch-all catch handler. Doing this is useful if you are calling into some other libraries that artinot .NET-aware and that might throw exceptions. However, it is included in the example to illustrate the principle.
Now that you have analyzed the code for the example, you can run it. The following output illustrates what happens with different inputs and demonstrates both the IndexOutOfRangeException and the FormatException being thrown:
Catching Exceptions from Other Code
The previous example demonstrated the handling of two exceptions. One of them, IndexOutOfRangeException, was thrown by your own code. The other, ForrnatException, was thrown from inside one of the base classes. It is very common for code in a library to throw an exception if it detects that some problem has occurred, or if one of the’methods has been called inappropriately by being passed the wrong parameters. However, library code rarely attempts to catch exceptions; this is regarded as the responsibility of the client code.
Often, you will find that exceptions are thrown from the base class libraries while you are debugging. The process of debugging to some extent involves determining why exceptions. have been thrown and removing the causes. Your aim should be to ensure that by the time the code is actually shipped; exceptions do occur only in very exceptional circumstances, and if at all possible, are handled in some appropriate way in your code.
The example has illustrated the use of only the Message property of the exception object. However, a number of other properties are available in System. Exception, as shown in the following table.
Of these properties, StackTrace and TargetSite are supplied automatically by the .NET runtime if a stack trace is available. Source will always be filled in by the .NET runtime as the name of the assembly in which Dll exception was raised (though you might want to modify the property in your code to give more specific information), whereas Data, Message, HelpLink, and InnerException must be filled in by the code that threw the exception, by setting these properties immediately before throwing the exception. For example, the code to throw an exception might look something like this:
Here, ClassMyException is the name of the particular exception class you are throwing. Note that it common practice for the names of all exception classes to end with Exception. Also note that the Data property is assigned in two possible ways.
What Happens If an Exception Isn’t Handled?
Sometimes an exception might be thrown, but there might not be a catch block in your code that is able to handle that kind of exception. The SimpleExceptions example can serve to illustrate this. Suppose, for example, that you omitted the FormatException and catch-all catch blocks, and supplied only the block that traps an IndexOutOfRangeException. In that circumstance, what would happen if a FormatException were thrown?
The answer is that the .NET Runtime would catch it. Later in this section, you learn how you can nest try blocks, and in fact, there is already a nested try block behind the scenes in the example. The .NET Runtime has effectively placed the entire program inside another huge try block – it does this for every .NET program. This try block has a catch handler that can catch any type of exception. If an exception occurs that your code does not handle, the execution flow will simply pass right out of your program and be trapped by this catch block in the .NET runtime. However, the results of this probably will not be what you want. What happens is that the execution of your code will be terminated promptly; the user will see a dialog box that complains that your code has not handled the exception, and that provides any details about the exception the .NET runtime was able to retrieve. At least the exception will have been caught though This is what actually happened earlier in the Vector example when the program threw an exception.
In general, if you are writing an executable, try to catch as many exceptions as you reasonably can and handle them in a sensible way. If you are writing a library, it is normally best not to handle exceptions (unless a particular exception represents something wrong in your code that you can handle), but instead, assume that the calling code will handle any errors it encounters. However, you may nevertheless want to catch any Microsoft-defined exceptions, so that you can throw your own exception objects that give more specific information to the client code.
Nested try Blocks
One nice feature of exceptions is that you can nest try blocks inside each other, like this:
Although each try block is accompanied by only one catch block in this example, you could string several catch blocks together, too. This section takes a closer look at how nested try blocks work.
If an exception is thrown inside the outer try block but outside the inner try block (points A anti 01. the situation is no different from any of the scenarios you have seen before: either the exception is caught by the outer catch block and the outer finally block is executed, or the finally block is executed and the .NET runtime handles the exception.
If an exception is thrown in the inner try block (point B), and there is a suitable inner catch block to handle the exception, then, again, you are in familiar territory: the exception is handled there, and ~ . inner finally block is executed before execution resumes inside the outer try block (at point D).
Now suppose that an exception occurs in the inner try block, but there ;m’t a suitable inner catch block to handle it. This time, the inner finally block is executed as usual, but then the .NET runtime will have no choice but to leave the entire inner try block in order to search for a suitable exception handler. The next obvious place to look is in the outer catch block. If the system finds one here, then that handler will be executed and then the outer finally block will be executed after. If there no suitable handler here, the search for one will go on. In this case, It means the outer finally block will be executed, and then, because there are no more catch blocks, control will be transferred to the .NET runtime. Note that a point is the code beyond point 0 in the outer try block executed.
An even more interesting thing happens If an exception is thrown at point C. If the program that point C, it mUit already processing an exception that was thrown at point B. It quite legitimate to throw another exception from inside a catch block. In this case, the exception 11treated as if it had been thrown by the outer try block, 10 flow of execution will Immediately leave the inner catch block, and execute the finally block, before the system searches the outer catch block for a handler. Similarly, if an exception is thrown in the inner finally block, control will immediately be transferred to the best appropriate handler, with the search starting at the outer catch block.
It is perfectly legitimate to throw exception from catch and finally blocks.
Although the situation has been shown with just two try blocks, the same principles hold no matter how many try blocks you nest inside each other. At each stage, the.NET . .m time will smoothly transfer control up through the try blocks, looking for an appropriate handler, At each stage, as control leaves a catch block, any cleanup code in the corresponding finally block (if present) will be executed, but no code outside any finally block will be run until the correct catch handler has been found and run.
The nesting of try blocks can also occur between methods themselves. For example, if method A calls method B from within a try block, then method B itself has a try block within it as well.
You have now seen how having nested try blocks can work. The obvious next question is why would you want to do that? There are two reasons:
- To modify the type of exception thrown.
- To enable different types of exception to be handled in different places in your code.
Modifying the Type of Exception
Modifying the type of the exception can be useful when the original exception thrown does not adequately describe the problem. What typically happens is that something – possibly the .NET runtime – throws a fairly low-level exception that says something like an overflow occurred (OverflowException) or an argument passed to a method was incorrect (a class derived from ArgumentException). However, because of the context in which the exception occurred, you will know that this reveals some other underlying problem (for example, an overflow can only have happened at that point 10 your code because a file you have just read contained incorrect data). In that case, the most appropriate thing that your handler for the first exception can do is throw another exception to that more accurately describes the problem, so that another catch block further along can deal with it more appropriately. In this cue, it can also forward the original exception through a property implemented by yet_. Exception called IMerException. IMerException simply contains a reference to any other .exception that thrown in class. the ultimate handler routine will need.this extra exception.
Of course, the situation also execute where an exception occurs inside a catch block. For example, you might normally read in some configuration file that contains detailed instructions for handling the error, and it might turn out that this full not there.·
Handling Different Exception In Different Places
The second reason for having nested try blocks is so that different types of exceptions can be handled at different location in your code. A good example of this is if you have a loop where various exception condition can occur. Some of these might be serious enough that you need to abandon the entire loop, whereat others might be leis serious and limply require that you on that iteration and move on to the next iteration around the loop. You could achieve this by having try block inside the loop, which handler the Dll serious error conditions, and an outer try block outside the loop. which handles the more various error condition. You will see how this works In the next exception. example.