finally
BlockPrograms frequently request and release resources dynamically (i.e., at execution time). For example, a program that reads a file from disk first makes a file-open request (as we’ll see in Chapter 17, Files and Streams). If that request succeeds, the program reads the contents of the file. Operating systems typically prevent more than one program from manipulating a file at once. Therefore, when a program finishes processing a file, the program should close the file (i.e., release the resource) so other programs can use it. If the file is not closed, a resource leak occurs. In such a case, the file resource is not available to other programs.
In programming languages such as C and C++, in which the programmer is responsible for dynamic memory management, the most common type of resource leak is a memory leak. A memory leak occurs when a program allocates memory (as C# programmers do via keyword new
), but does not deallocate the memory when it’s no longer needed. Normally, this is not an issue in C#, because the CLR performs garbage collection of memory that’s no longer needed by an executing program (Section 10.8). However, other kinds of resource leaks (such as unclosed files) can occur.
The CLR does not completely eliminate memory leaks. It will not garbage-collect an object until the program contains no more references to that object, and even then there may be a delay until the memory is required. Thus, memory leaks can occur if you inadvertently keep references to unwanted objects.
finally
BlockExceptions often occur when an app uses resources that require explicit release. For example, a program that processes a file might receive IOException
s during the processing. For this reason, file-processing code normally appears in a try
block. Regardless of whether a program experiences exceptions while processing a file, the program should close the file when it’s no longer needed. Suppose a program places all resource-request and resource-release code in a try
block. If no exceptions occur, the try
block executes normally and releases the resources after using them. However, if an exception occurs, the try
block may exit before the resource-release code can execute. We could duplicate all the resource-release code in each of the catch
blocks, but this would make the code more difficult to modify and maintain. We could also place the resource-release code after the try
statement; however, if the try
block terminated due to a return
statement or an exception occurred, code following the try
statement would never execute.
To address these problems, C#’s exception-handling mechanism provides the finally
block, which is guaranteed to execute regardless of whether the try
block executes successfully or an exception occurs. This makes the finally
block an ideal location in which to place resource-release code for resources that are acquired and manipulated in the corresponding try
block:
If the try
block executes successfully, the finally
block executes immediately after the try
block terminates—either by reaching the block’s closing brace or if a return
statement executes in the block.
If an exception occurs in the try
block, the finally
block executes immediately after a catch
block completes—either by reaching the block’s closing brace or if a return
statement executes in the block.
If there is no catch
block, if the exception is not caught by a catch
block associated with the try
block, or if a catch
block associated with the try
block throws an exception itself, the finally
block executes before the exception is processed by the next enclosing try
block, which could be in the calling method.
By placing the resource-release code in a finally
block, we ensure that even if the program terminates due to an uncaught exception, the resource will be deallocated. Local variables in a try
block cannot be accessed in the corresponding finally
block. For this reason, variables that must be accessed in both a try
block and its corresponding finally
block should be declared before the try
block.
A finally
block typically contains code to release resources acquired in the corresponding try
block, which makes the finally
block an effective mechanism for eliminating resource leaks.
As a rule, resources should be released as soon as they’re no longer needed in a program. This makes them available for reuse promptly.
If one or more catch
blocks follow a try
block, the finally
block is optional. However, if no catch
blocks follow a try
block, a finally
block must appear immediately after the try
block. If any catch
blocks follow a try
block, the finally
block (if there is one) appears after the last catch
block. Only whitespace and comments can separate the blocks in a try
statement.
finally
BlockThe app in Fig. 13.4 demonstrates that the finally
block always executes, regardless of whether an exception occurs in the corresponding try
block. The app consists of method Main
(lines 8–47) and four other methods that Main
invokes to demonstrate finally
. These methods are DoesNotThrowException
(lines 50–67), ThrowExceptionWithCatch
(lines 70–89), ThrowExceptionWithoutCatch
(lines 92–108) and ThrowExceptionCatchRethrow
(lines 111–136).
Line 12 of Main
invokes method DoesNotThrowException
. This method’s try
block outputs a message (line 55). Because the try
block does not throw any exceptions, program control ignores the catch
block (lines 57–60) and executes the finally
block (lines 61–64), which outputs a message. At this point, program control continues with the first statement after the close of the finally
block (line 66), which outputs a message indicating that the end of the method has been reached. Then program control returns to Main
.
throw
StatementLine 16 of Main
invokes method ThrowExceptionWithCatch
(lines 70–89), which begins in its try
block (lines 73–77) by outputting a message. Next, the try
block creates an Exception
object and uses a throw
statement to throw it (line 76). Executing the throw
statement indicates that a problem has occurred in the code. As you’ve seen in earlier chapters, you can throw exceptions by using the throw
statement. Just as with exceptions thrown by the Framework Class Library’s methods and the CLR, this indicates to client apps that an error has occurred. A throw
statement specifies an object to be thrown. The operand of a throw
statement can be of type Exception
or of any type derived from it.
The string
passed to the constructor becomes the exception object’s error message. When a throw
statement in a try
block executes, the try
block exits immediately, and program control continues with the first matching catch
block (lines 78–81) following the try
block. In this example, the type thrown (Exception
) matches the type specified in the catch
, so line 80 outputs a message indicating the exception that occurred. Then, the finally
block (lines 82–86) executes and outputs a message. At this point, program control continues with the first statement after the close of the finally
block (line 88), which outputs a message indicating that the end of the method has been reached. Program control then returns to Main
. In line 80, we use the exception object’s Message
property to retrieve the error message associated with the exception (i.e., the message passed to the Exception
constructor). Section 13.7 discusses several properties of class Exception
.
Lines 23–31 of Main
define a try
statement in which Main
invokes method Throw-ExceptionWithoutCatch
(lines 92–108). The try
block enables Main
to catch any exceptions thrown by ThrowExceptionWithoutCatch
. The try
block in lines 95–99 of ThrowExceptionWithoutCatch
begins by outputting a message. Next, the try
block throws an Exception
(line 98) and exits immediately.
Normally, program control would continue at the first catch
following this try
block. However, this try
block does not have any catch
blocks. Therefore, the exception is not caught in method ThrowExceptionWithoutCatch
. Program control proceeds to the finally
block (lines 100–104), which outputs a message. At this point, program control returns to Main
in search of an appropriate catch
block—any statements appearing after the finally
block (e.g., line 107) do not execute. (In fact, the compiler issues a warning about this.) In this example, such statements could cause logic errors, because the exception thrown in line 98 is not caught. In Main
, the catch
block in lines 27–31 catches the exception and displays a message indicating that the exception was caught in Main
.
Lines 38–46 of Main
define a try
statement in which Main
invokes method Throw-ExceptionCatchRethrow
(lines 111–136). The try
statement enables Main
to catch any exceptions thrown by ThrowExceptionCatchRethrow
. The try
statement in lines 114–132 of ThrowExceptionCatchRethrow
begins by outputting a message. Next, the try
block throws an Exception
(line 117). The try
block exits immediately, and program control continues at the first catch
(lines 119–127) following the try
block. In this example, the type thrown (Exception
) matches the type specified in the catch
, so line 121 outputs a message indicating where the exception occurred. Line 124 uses the throw
statement to rethrow the exception. This indicates that the catch
block performed partial processing of the exception and now is throwing the exception again (in this case, back to the method Main
) for further processing.
You also can rethrow an exception with a version of the throw
statement which takes an operand that’s the reference to the exception that was caught. It’s important to note, however, that this form of throw
statement resets the throw point, so the original throw point’s stack-trace information is lost. Section 13.7 demonstrates using a throw
statement with an operand from a catch
block. In that section, you’ll see that after an exception is caught, you can create and throw a different type of exception object from the catch
block and you can include the original exception as part of the new exception object. Class library designers often do this to customize the exception types thrown from methods in their class libraries or to provide additional debugging information.
In general, it’s considered better practice to throw a new exception and pass the original one to the new exception’s constructor, rather than rethrowing the original exception. This maintains all of the stack-trace information from the original exception. We demonstrate passing an existing exception to a new exception’s constructor in Section 13.7.3..
The exception handling in method ThrowExceptionCatchRethrow
does not complete, because the throw
statement in line 124 immediately terminates the catch
block—if there were any code between line 124 and the end of the block, it would not execute. When line 124 executes, method ThrowExceptionCatchRethrow
terminates and returns control to Main
in search of an appropriate catch
. Once again, the finally
block (lines 128–132) executes and outputs a message before control returns to Main
. When control returns to Main
, the catch
block in lines 42–46 catches the exception and displays a message indicating that the exception was caught. Then the program terminates.
finally
BlockThe next statement to execute after a finally
block terminates depends on the exception-handling state. If the try
block successfully completes, or if a catch
block catches and handles an exception, the program continues its execution with the next statement after the finally
block. However, if an exception is not caught, or if a catch
block rethrows an exception, program control continues in the next enclosing try
block. The enclosing try
could be in the calling method or in one of its callers. It also is possible to nest a try
statement in a try
block; in such a case, the outer try
statement’s catch
blocks would process any exceptions that were not caught in the inner try
statement. If a try
block executes and has a corresponding finally
block, the finally
block executes even if the try
block terminates due to a return
statement. The return
occurs after the execution of the finally
block.
If an uncaught exception is awaiting processing when the finally
block executes, and the finally
block throws a new exception that’s not caught in the finally
block, the first exception is lost, and the new exception is passed to the next enclosing try
block.
When placing code that can throw an exception in a finally
block, always enclose the code in a try
statement that catches the appropriate exception types. This prevents the loss of any uncaught and rethrown exceptions that occur before the finally
block executes.
Do not place try
blocks around every statement that might throw an exception—this can make programs difficult to read. Instead, place one try
block around a significant portion of code, and follow this try
block with catch
blocks that handle each possible exception. Then follow the catch
blocks with a single finally
block. Use separate try
blocks to distinguish between multiple statements that can throw the same exception type.