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:
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:
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:
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:
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.
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.
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.
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
PropertiesThe 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.
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.
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.
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.
Typically you use ToString
just to show information, whereas you use properties to analyze exception information.
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:
Try..Catch..Finally
BlocksYou 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:
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
StatementsYou 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:
3.149.231.128