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 18, 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, possibly because a program using the file has not closed it.
In programming languages such as C and C++, in which the programmer (not the language) 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 is no longer needed. Normally, this is not an issue in C#, because the CLR performs garbage collection of memory that is no longer needed by an executing program (Section 9.10). However, other kinds of resource leaks (such as unclosed files) can occur.
Moving Resource Release Code to a finally Block
Typically, exceptions occur when processing 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 is 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 expire 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 terminates due to a return statement, 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. If an exception occurs in the try block, the finally block executes immediately after a catch block completes. 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 (if there is one). 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. Note that 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.
|
|
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.
|
Demonstrating the finally Block
The application in Fig. 12.4 demonstrates that the finally block always executes, regardless of whether an exception occurs in the corresponding try block. The program consists of method Main (lines 847) and four other methods that Main invokes to demonstrate finally. These methods are DoesNotThrowException (lines 5067), ThrowExceptionWithCatch (lines 7089), ThrowExceptionWithoutCatch (lines 92108) and ThrowExceptionCatchRethrow (lines 111136).
Figure 12.4. finally blocks always execute, regardless of whether an exception occurs.
1 // Fig. 12.4: UsingExceptions.cs 2 // Using finally blocks. 3 // Demonstrate that finally always executes. 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 } // end try 27 catch 28 { 29 Console.WriteLine( "Caught exception from " + 30 "ThrowExceptionWithoutCatch in Main" ); 31 } // end catch 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 } // end try 42 catch 43 { 44 Console.WriteLine( "Caught exception from " + 45 "ThrowExceptionCatchRethrow in Main" ); 46 } // end catch 47 } // end method Main 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 } // end try 57 catch 58 { 59 Console.WriteLine( "This catch never executes" ); 60 } // end catch 61 finally 62 { 63 Console.WriteLine( "finally executed in DoesNotThrowException" ); 64 } // end finally 65 66 Console.WriteLine( "End of DoesNotThrowException" ); 67 } // end method DoesNotThrowException 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 } // end try 78 catch ( Exception exceptionParameter ) 79 { 80 Console.WriteLine( "Message: " + exceptionParameter.Message ); 81 } // end catch 82 finally 83 { 84 Console.WriteLine( 85 "finally executed in ThrowExceptionWithCatch" ); 86 } // end finally 87 88 Console.WriteLine( "End of ThrowExceptionWithCatch" ); 89 } // end method ThrowExceptionWithCatch 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 } // end try 100 finally 101 { 102 Console.WriteLine( "finally executed in " + 103 "ThrowExceptionWithoutCatch" ); 104 } // end finally 105 106 // unreachable code; logic error 107 Console.WriteLine( "End of ThrowExceptionWithoutCatch" ); 108 } // end method ThrowExceptionWithoutCatch 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 } // end try 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 } // end catch 128 finally 129 { 130 Console.WriteLine( "finally executed in " + 131 "ThrowExceptionCatchRethrow" ); 132 } // end finally 133 134 // any code placed here is never reached 135 Console.WriteLine( "End of ThrowExceptionCatchRethrow" ); 136 } // end method ThrowExceptionCatchRethrow 137 } // end class UsingExceptions
|
Line 12 of Main invokes method DoesNotThrowException. The try block for this method outputs a message (line 55). Because the TRy block does not throw any exceptions, program control ignores the catch block (lines 5760) and executes the finally block (lines 6164), 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.
Throwing Exceptions Using the throw Statement
Line 16 of Main invokes method THRowExceptionWithCatch (lines 7089), which begins in its TRy block (lines 7377) by outputting a message. Next, the TRy block creates an Exception object and uses a throw statement to throw the exception object (line 76). Executing the throw statement indicates that an exception has occurred. So far you have only caught exceptions thrown by called methods. You can throw exceptions by using the tHRow statement. Just as with exceptions thrown by the FCL's methods and the CLR, this indicates to client applications 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 class Exception.
|
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 expires immediately, and program control continues with the first matching catch block (lines 7881) 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 8286) 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, note that 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 12.7 discusses several properties of class Exception.
Lines 2331 of Main define a try statement in which Main invokes method ThrowExceptionWithoutCatch (lines 92108). The try block enables Main to catch any exceptions thrown by ThrowExceptionWithoutCatch. The try block in lines 9599 of ThrowExceptionWithoutCatch begins by outputting a message. Next, the try block throws an Exception (line 98) and expires 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 100104), which outputs a message. At this point, program control returns to Mainany statements appearing after the finally block (e.g., line 107) do not execute. 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 2731 catches the exception and displays a message indicating that the exception was caught in Main.
Rethrowing Exceptions
Lines 3846 of Main define a TRy statement in which Main invokes method THRowExceptionCatchRethrow (lines 111136). The TRy statement enables Main to catch any exceptions thrown by THRowExceptionCatchRethrow. The try statement in lines 114132 of THRowExceptionCatchRethrow begins by outputting a message. Next, the TRy block throws an Exception (line 117). The try block expires immediately, and program control continues at the first catch (lines 119127) 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 passing the exception back to the calling method (in this case, Main) for further processing.
You can also rethrow an exception with a version of the tHRow statement which takes an operand that is the reference to the exception that was caught. It is 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 12.7 demonstrates using a tHRow statement with an operand from a catch block. In that section, you will 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.
The exception handling in method THRowExceptionCatchRethrow does not complete, because the program cannot run code in the catch block placed after the invocation of the throw statement in line 124. Therefore, method THRowExceptionCatchRethrow terminates and returns control to Main. Once again, the finally block (lines 128132) executes and outputs a message before control returns to Main. When control returns to Main, the catch block in lines 4246 catches the exception and displays a message indicating that the exception was caught. Then the program terminates.
Returning After a finally Block
Note that 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.
|
|
|
The using Statement
Recall from earlier in this section that resource-release code should be placed in a finally block to ensure that a resource is released, regardless of whether there were exceptions when the resource was used in the corresponding try block. An alternative notationthe using statement (not to be confused with the using directive for using namespaces)simplifies writing code in which you obtain a resource, use the resource in a try block and release the resource in a corresponding finally block. For example, a file processing application (Chapter 18) could process a file with a using statement to ensure that the file is closed properly when it is no longer needed. The resource must be an object that implements the IDisposable interface and therefore has a Dispose method. The general form of a using statement would be
using ( ExampleObject exampleObject = new ExampleObject() ) { exampleObject.SomeMethod(); }
where ExampleObject is a class that implements the IDisposable interface. This code creates an object of type ExampleObject and uses it in a statement, then calls its Dispose method to release any resources used by the object. The using statement implicitly places the code in its body in a TRy block with a corresponding finally block that calls the object's Dispose method. For instance, the preceding code is equivalent to
{ ExampleObject exampleObject = new ExampleObject(); try { exampleObject.SomeMethod(); } finally { if ( exampleObject != null ) ( ( IDisposable ) exampleObject ).Dispose(); } }
Note that the if statement ensures that exampleObject still references an object; otherwise, a NullReferenceException might occur. You can read more about the using statement in the C# Language Specification Section 8.13 (Section 15.13 in the ECMA version).
Exception Properties |
Preface
Index
Introduction to Computers, the Internet and Visual C#
Introduction to the Visual C# 2005 Express Edition IDE
Introduction to C# Applications
Introduction to Classes and Objects
Control Statements: Part 1
Control Statements: Part 2
Methods: A Deeper Look
Arrays
Classes and Objects: A Deeper Look
Object-Oriented Programming: Inheritance
Polymorphism, Interfaces & Operator Overloading
Exception Handling
Graphical User Interface Concepts: Part 1
Graphical User Interface Concepts: Part 2
Multithreading
Strings, Characters and Regular Expressions
Graphics and Multimedia
Files and Streams
Extensible Markup Language (XML)
Database, SQL and ADO.NET
ASP.NET 2.0, Web Forms and Web Controls
Web Services
Networking: Streams-Based Sockets and Datagrams
Searching and Sorting
Data Structures
Generics
Collections
Appendix A. Operator Precedence Chart
Appendix B. Number Systems
Appendix C. Using the Visual Studio 2005 Debugger
Appendix D. ASCII Character Set
Appendix E. Unicode®
Appendix F. Introduction to XHTML: Part 1
Appendix G. Introduction to XHTML: Part 2
Appendix H. HTML/XHTML Special Characters
Appendix I. HTML/XHTML Colors
Appendix J. ATM Case Study Code
Appendix K. UML 2: Additional Diagram Types
Appendix L. Simple Types
Index