13.5 finally Block

Programs 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.

Error-Prevention Tip 13.2

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.

13.5.1 Moving Resource-Release Code to a finally Block

Exceptions often occur when an app uses resources that require explicit release. For example, a program that processes a file might receive IOExceptions 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.

Error-Prevention Tip 13.3

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.

 

Performance Tip 13.1

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.

13.5.2 Demonstrating the finally Block

The 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).

Fig. 13.4 finally blocks always execute, even when no exception occurs.

Alternate View

  1    // Fig. 13.4: UsingExceptions.cs
  2    // finally blocks always execute, even when no exception occurs.
  3
  4    using System;
  5
  6    class UsingExceptions
  7    {
  8       static void Main()
  9       {
 10          // Case 1: No exceptions occur in called method
 11          Console.WriteLine("Calling DoesNotThrowException");
 12          DoesNotThrowException();
 13
 14          // Case 2: Exception occurs and is caught in called method
 15          Console.WriteLine("
Calling ThrowExceptionWithCatch");
 16          ThrowExceptionWithCatch();
 17
 18          // Case 3: Exception occurs, but is not caught in called method
 19          // because there is no catch block.
 20           Console.WriteLine("
Calling ThrowExceptionWithoutCatch");
 21
 22          // call ThrowExceptionWithoutCatch
 23          try
 24          {
 25             ThrowExceptionWithoutCatch();
 26          }
 27          catch
 28          {
 29              Console.WriteLine(
 30                 "Caught exception from ThrowExceptionWithoutCatch in Main");
 31          }
 32
 33          // Case 4: Exception occurs and is caught in called method,
 34          // then rethrown to caller.
 35          Console.WriteLine("
Calling ThrowExceptionCatchRethrow");
 36
 37          // call ThrowExceptionCatchRethrow
 38          try
 39          {
 40             ThrowExceptionCatchRethrow();
 41          }
 42          catch
 43          {
 44              Console.WriteLine(
 45                 "Caught exception from ThrowExceptionCatchRethrow in Main");
 46          }
 47       }
 48
 49       // no exceptions thrown
 50       static void DoesNotThrowException()
 51       {
 52           // try block does not throw any exceptions
 53           try
 54           {
 55               Console.WriteLine("In DoesNotThrowException");
 56           }
 57           catch
 58           {
 59              Console.WriteLine("This catch never executes");
 60           }
 61           finally
 62           {
 63              Console.WriteLine("finally executed in DoesNotThrowException");
 64           }
 65
 66           Console.WriteLine("End of DoesNotThrowException");
 67         }
 68
 69         // throws exception and catches it locally
 70         static void ThrowExceptionWithCatch()
 71         {
 72            // try block throws exception
 73            try
 74            {
 75                Console.WriteLine("In ThrowExceptionWithCatch");
 76               throw new Exception("Exception in ThrowExceptionWithCatch");
 77            }
 78            catch (Exception exceptionParameter)
 79            {
 80                Console.WriteLine($"Message: {exceptionParameter.Message}");
 81            }
 82            finally
 83            {
 84                Console.WriteLine(
 85                   "finally executed in ThrowExceptionWithCatch");
 86            }
 87
 88            Console.WriteLine("End of ThrowExceptionWithCatch");
 89         }
 90
 91         // throws exception and does not catch it locally
 92         static void ThrowExceptionWithoutCatch()
 93         {
 94            // throw exception, but do not catch it
 95            try
 96            {
 97                Console.WriteLine("In ThrowExceptionWithoutCatch");
 98                throw new Exception("Exception in ThrowExceptionWithoutCatch");
 99            }
 100           finally
 101           {
 102               Console.WriteLine(
 103                  "finally executed in ThrowExceptionWithoutCatch");
 104           }
 105
 106           // unreachable code; logic error
 107           Console.WriteLine("End of ThrowExceptionWithoutCatch");
 108        }
 109
 110        // throws exception, catches it and rethrows it
 111        static void ThrowExceptionCatchRethrow()
 112        {
 113           // try block throws exception
 114           try
 115           {
 116               Console.WriteLine("In ThrowExceptionCatchRethrow");
 117              throw new Exception("Exception in ThrowExceptionCatchRethrow");
 118           }
 119           catch (Exception exceptionParameter)
 120           {
 121               Console.WriteLine("Message: " + exceptionParameter.Message);
 122
 123               // rethrow exception for further processing
 124               throw;
 125
 126               // unreachable code; logic error
 127            }
 128            finally
 129            {
 130               Console.WriteLine(
 131                  "finally executed in ThrowExceptionCatchRethrow");
 132            }
 133
 134            // any code placed here is never reached
 135            Console.WriteLine("End of ThrowExceptionCatchRethrow");
 136       }
 137    }

Calling DoesNotThrowException
In DoesNotThrowException
finally executed in DoesNotThrowException
End of DoesNotThrowException

Calling ThrowExceptionWithCatch
In ThrowExceptionWithCatch
Message: Exception in ThrowExceptionWithCatch
finally executed in ThrowExceptionWithCatch
End of ThrowExceptionWithCatch

Calling ThrowExceptionWithoutCatch
In ThrowExceptionWithoutCatch
finally executed in ThrowExceptionWithoutCatch
Caught exception from ThrowExceptionWithoutCatch in Main

Calling ThrowExceptionCatchRethrow
In ThrowExceptionCatchRethrow
Message: Exception in ThrowExceptionCatchRethrow
finally executed in ThrowExceptionCatchRethrow
Caught exception from ThrowExceptionCatchRethrow in Main

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.

13.5.3 Throwing Exceptions Using the throw Statement

Line 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.

13.5.4 Rethrowing Exceptions

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.

Software Engineering Observation 13.2

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.

13.5.5 Returning After a finally Block

The 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.

Common Programming Error 13.3

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.

 

Error-Prevention Tip 13.4

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.

 

Software Engineering Observation 13.3

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.

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

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