Try..Catch..Finally

You perform exception handling writing a Try..Catch..Finally code block. The logic is that you say to the compiler: “Try to execute the code; if you encounter an exception, take the specified actions; whenever the code execution succeeds or it fails due to an exception, execute the final code.” The most basic code for controlling the execution flow regarding exceptions is the following:

image

IntelliSense does a great job here. When you type the Try keyword and then press Enter, it automatically adds the Catch statement and the End Try terminator. The ex variable gets the instance of the System.Exception that is caught and that provides important information so that you can best handle the exception. To see what happens, consider the following code snippet:

image

Here we have an array of strings in which the upper range of the array is 3. The Try block tries to execute code that attempts writing the content of the fourth index to the Console window. Unfortunately, such an index does not exist, but because the code is formally legal, it will be correctly compiled. When the application runs and the runtime encounters this situation, it throws an exception to communicate the error occurrence. So the Catch statement intercepts the exception and enables deciding what actions must be taken. In our example the action to handle the exception is to write the complete error message of the exception. If the code within Try succeeds, the execution passes to the first code after the End Try terminator. In our example the control transfers to the Catch block that contains code that writes to the Console window the actual error message that looks like the following:

Index was outside the bounds of the array

Basically the runtime never throws a generic System.Exception exception. There are specific exceptions for the most common scenarios (and it is worth mentioning that you can create custom exceptions as discussed in Chapter 12, “Inheritance”) that are helpful to identify what happened instead of inspecting a generic exception. Continuing our example, the runtime throws an IndexOutOfRangeException that means the code attempted to access and index greater or smaller than allowed. Based on these considerations, the code could be rewritten as follows:

image

As you can see, the most specific exception needs to be caught before the most generic one. This is quite obvious, because if you first catch the System.Exception, all other exceptions will be ignored. Intercepting specific exceptions can also be useful because you can both communicate the user detailed information, and you can decide what actions must be taken to solve the problem. Anyway, always adding a Catch block for a generic System.Exception is a best practice. This allows providing a general error handling code in case exceptions that you do not specifically intercept will occur. You could also need to perform some actions independently from the result of your code execution. The Finally statement enables executing some code either if the Try succeeds or if it fails, passing control to Catch. For example you might want to clean up resources used by the array:

image

Notice how objects referred within the Finally block must be declared outside the Try..End Try block because of visibility. The code within Finally will be executed whatever will be the result of the Try block. This is important; for example, think about files. You might open a file and then try to perform some actions on the file that for any reason can fail. In this situation you would need to close the file, and Finally ensures you can do that both if the file access is successful and if it fails (throwing an exception). An example of this scenario is represented in Listing 6.1.

Listing 6.1 Use Finally to Ensure Resources Are Freed Up and Unlocked

image

The code in Listing 6.1 is quite simple. First, it asks the user to specify a filename to be accessed. Accessing files is accomplished with a FileStream object. Notice that the myFile object is declared outside the Try..End Try block so that it can be visible within Finally. Moreover, its value is set to Nothing so that it has a default value, although null. If we did not assign this default value, myFile would just be declared but not yet assigned so the Visual Basic compiler would throw a warning message. By the way, setting the default value to Nothing will not prevent a NullReferenceException at runtime unless the variable gets a value. The Try block attempts accessing the specified file. The FileStream.Seek method here is just used as an example needed to perform an operation on the file. When accessing files there could be different problems, resulting in different kinds of exceptions. In our example, if the specified file does not exist, a FileNotFoundException is thrown by the runtime, and the Catch block takes control over the execution. Within the block we just communicate that the specified file was not found. If instead the file exists, the code performs a seeking. In both cases, the Finally block ensures that the file gets closed, independently on what happened before. This is fundamental, because if you leave a file opened, other problems would occur.

Exceptions Hierarchy

In Listing 6.1 we saw how we can catch a specific exception, such as FileNotFoundException, and then the general System.Exception. By the way, FileNotFoundException does not directly derive from System.Exception; instead, it derives from System.IO.IOException that is related to general input/output problems. Although you are not obliged to also catch an IOException when working with files, adding it could be a good practice because you can separate error handling for disk input/output errors from other errors. In such situations the rule is that you have to catch exceptions from the most specific to the most general. Continuing the previous example, Listing 6.2 shows how you can implement exceptions hierarchy.

Listing 6.2 Understanding Exceptions Hierarchy

image

FileNotFoundException is the most specific exception, so it must be caught first. It derives from IOException, which is intercepted as second. System.Exception is instead the base class for all exceptions and therefore must be caught last.

System.Exception Properties

The System.Exception class exposes some properties that are useful for investigating exceptions and then understanding what the real problem is. Particularly when you catch specialized exceptions, it could happen that such exception is just the last ring of a chain and that the problem causing the exception itself derives from other problems. System.Exception’s properties enable a better navigation of exceptions. Table 6.1 lists available properties.

Table 6.1 System.Exception’s Properties

image

Listing 6.3 shows how you can retrieve deep information on the exception. In the example, information is retrieved for the System.IO.FileNotFoundException, but you can use such properties for each exception you like.

Listing 6.3 Investigating Exceptions Properties

image

image

If we run this code and specify a filename that does not exist, we can retrieve a lot of useful information. Figure 6.2 shows the result of the code.

Figure 6.2 Getting information on the exception.

image

As you can see from Figure 6.2, the Message property contains the full error message. In production environments, this can be useful to provide customers a user-friendly error message. The Source property shows the application or object causing the exception. In our example it retrieves Mscorlib, meaning that the exception was thrown by the Common Language Runtime. The Target property retrieves the method in which the exception was thrown. It is worth mentioning that this property retrieves the native method that caused the error, meaning that the method was invoked by the Common Language Runtime. This is clearer if we take a look at the content of the Stack property. We can see the hierarchy of method calls in descending order: the .ctor method is the constructor of the FileStream class, invoked within the Main method; the next method, named Init, attempts to initialize a FileStream and is invoked behind the scenes by the CLR. Init and then invokes the native WinIOError function because accessing the file was unsuccessful. Analyzing such properties can be useful to understand what happened. Because there is no other useful information, iterating the Data property produced no result. By the way, if you just need to report a detailed message about the exception, you can collect most of the properties’ content just invoking the ToString method of the Exception class. For example, you could replace the entire Catch ex as FileNotFoundException block as follows:

Catch ex As FileNotFoundException
    Console.WriteLine(ex.ToString)

This edit produces the result shown in Figure 6.3. As you can see, the result is a little different from the previous one. We can find a lot of useful information, such as the name of the exception, complete error message, filename, Call Stack hierarchy, and line of code that caused the exception.

Figure 6.3 Invoking the Exception.ToString method offers detailed information.

image

Typically you use ToString just to show information, whereas you use properties to analyze exception information.

Check Values Instead of Catching Exceptions

Catching exceptions is necessary but it is also performances consuming. There are situations in which you could simply check the value of an object instead of catching exceptions. For example, you could check with an If..Then statement if an object is null instead of catching a NullReference exception (when possible, of course). Exceptions are best left to handle “exceptional” situations that occur in code, so you should limit the over use of them when possible.

You can also ignore exceptions by simply not writing anything inside a Catch block. For example, the following code catches a FileNotFoundException and prevents an undesired stop in the application execution but takes no action:

image

Nested Try..Catch..Finally Blocks

You also have the ability to nest Try..Catch..Finally code blocks. Nested blocks are useful when you have to try the execution of code onto the result of another Try..Catch block. Consider the following code that has the purpose of showing the creation time of all files in a given directory:

image

The first Try..Catch attempts reading the list of files from a specified directory. Because you may encounter directory errors, a DirectoryNotFoundException is caught. The result (being an array of String) is then iterated within a nested Try..Catch block. This is because the code is now working on files and then specific errors might be encountered.

Exit Try Statements

You can exit from within a Try..Catch..Finally block at any moment using an Exit Try statement. If there is a Finally block, Exit Try pulls the execution into Finally that otherwise resumes the execution at the first line of code after End Try. The following code snippet shows an example:

image

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

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