Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Exception-Handling Mechanics

In this section, I’ll introduce the mechanics and C# constructs needed in order to use exception handling, but it’s not my intention to explain them in great detail. The purpose of this chapter is to offer useful guidelines for when and how to use exception handling in your code. If you want more information about the mechanics and language constructs for using exception handling, see the .NET Framework documentation and the C# language specification. Also, the .NET Framework exception- handling mechanism is built using the Structured Exception Handling (SEH) mechanism offered by Windows. SEH has been discussed in many resources, including the book, Windows via C/C++, Fifth Edition, by myself and Christophe Nasarre (Microsoft Press, 2007), which contains three chapters devoted to SEH.

The following C# code shows a standard usage of the exception-handling mechanism. This code gives you an idea of what exception-handling blocks look like and what their purpose is. In the sub- sections after the code, I’ll formally describe the try, catch, and finally blocks and their purpose and provide some notes about their use.

private void SomeMethod() { try {

// Put code requiring graceful recovery and/or cleanup operations here...

}


catch (InvalidOperationException) {

// Put code that recovers from an InvalidOperationException here...

}

catch (IOException) {

// Put code that recovers from an IOException here...

}

catch {

// Put code that recovers from any kind of exception other than those preceding this...

 

// When catching any exception, you usually re­throw the exception.

// I explain re­throwing later in this chapter. throw;

}

finally {

// Put code that cleans up any operations started within the try block here...

// The code in here ALWAYS executes, regardless of whether an exception is thrown.

}

// Code below the finally block executes if no exception is thrown within the try block

// or if a catch block catches the exception and doesn't throw or re­throw an exception.

}

 

This code demonstrates one possible way to use exception-handling blocks. Don’t let the code scare you—most methods have simply a try block matched with a single finally block or a try block matched with a single catch block. It’s unusual to have as many catch blocks as in this ex- ample. I put them there for illustration purposes.

 

Thetry Block

A try block contains code that requires common cleanup operations, exception-recovery operations, or both. The cleanup code should be placed in a single finally block. A try block can also contain code that might potentially throw an exception. The exception-recovery code should be placed in one or more catch blocks. You create one catch block for each kind of exception that your application can safely recover from. A try block must be associated with at least one catch or finally block; it makes no sense to have a try block that stands by itself, and C# will prevent you from doing this.



       
   
 
 


Thecatch Block

A catch block contains code to execute in response to an exception. A try block can have zero or more catch blocks associated with it. If the code in a try block doesn’t cause an exception to be thrown, the CLR will never execute the code contained within any of its catch blocks. The thread will simply skip over all of the catch blocks and execute the code in the finally block (if one exists).

After the code in the finally block executes, execution continues with the statement following the

finally block.

 

The parenthetical expression appearing after the catch keyword is called the catch type. In C#, you must specify a catch type of System.Exception or a type derived from System.Exception. For example, the previous code contains catch blocks for handling an InvalidOperationException

(or any exception derived from it) and an IOException (or any exception derived from it). The last catch block (which doesn’t specify a catch type) handles any exception except for the exception type specified by earlier catch blocks; this is equivalent to having a catch block that specifies a catch type of System.Exception except that you cannot access the exception information via code inside the catch block’s braces.

       
   
 
 

 

The CLR searches from top to bottom for a matching catch type, and therefore you should place the more specific exception types at the top. The most-derived exception types should appear first, followed by their base types (if any), down to System.Exception (or an exception block that doesn’t specify a catch type). In fact, the C# compiler generates an error if more specific catch blocks appear closer to the bottom because the catch block would be unreachable.

If an exception is thrown by code executing within the try block (or any method called from within the try block), the CLR starts searching for catch blocks whose catch type is the same type as or a base type of the thrown exception. If none of the catch types matches the exception, the CLR continues searching up the call stack looking for a catch type that matches the exception. If after reaching the top of the call stack, no catch block is found with a matching catch type, an unhandled exception occurs. I’ll talk more about unhandled exceptions later in this chapter.

After the CLR locates a catch block with a matching catch type, it executes the code in all inner finally blocks, starting from within the try block whose code threw the exception and stopping with the catch block that matched the exception. Note that any finally block associated with the catch block that matched the exception is not executed yet. The code in this finally block won’t execute until after the code in the handling catch block has executed.


After all the code in the inner finally blocks has executed, the code in the handling catch block executes. This code typically performs some operations to deal with the exception. At the end of the catch block, you have three choices:

■ Re-throw the same exception, notifying code higher up in the call stack of the exception.

■ Throw a different exception, giving richer exception information to code higher up in the call stack.

■ Let the thread fall out of the bottom of the catch block.

Later in this chapter, I’ll offer some guidelines for when you should use each of these techniques. If you choose either of the first two techniques, you’re throwing an exception, and the CLR behaves just as it did before: it walks up the call stack looking for a catch block whose type matches the type of the exception thrown.

If you pick the last technique, when the thread falls out of the bottom of the catch block, it im- mediately starts executing code contained in the finally block (if one exists). After all of the code in the finally block executes, the thread drops out of the finally block and starts executing the statements immediately following the finally block. If no finally block exists, the thread contin- ues execution at the statement following the last catch block.

In C#, you can specify a variable name after a catch type. When an exception is caught, this vari- able refers to the System.Exception-derived object that was thrown. The catch block’s code can reference this variable to access information specific to the exception (such as the stack trace leading up to the exception). Although it’s possible to modify this object, you shouldn’t; consider the object to be read-only. I’ll explain the Exception type and what you can do with it later in this chapter.

       
   
 
 

Thefinally Block

A finally block contains code that’s guaranteed to execute.2 Typically, the code in a finally block performs the cleanup operations required by actions taken in the try block. For example, if you open a file in a try block, you’d put the code to close the file in a finally block.

private void ReadData(String pathname) { FileStream fs = null;

try {

fs = new FileStream(pathname, FileMode.Open);

 

 
 

2 Aborting a thread or unloading an AppDomain causes the CLR to throw a ThreadAbortException, which allows the finally block to execute. If a thread is simply killed via the Win32 TerminateThread function, or if the process is killed via the Win32 TerminateProcess function or System.Environment’s FailFast method, then the finally block will not execute. Of course Windows cleans up all resources that a process was using when a process terminates.


// Process the data in the file...

}

catch (IOException) {

// Put code that recovers from an IOException here...

}

finally {

// Make sure that the file gets closed. if (fs != null) fs.Close();

}

}

 

If the code in the try block executes without throwing an exception, the file is guaranteed to be closed. If the code in the try block does throw an exception, the code in the finally block still executes, and the file is guaranteed to be closed, regardless of whether the exception is caught. It’s improper to put the statement to close the file after the finally block; the statement wouldn’t ex- ecute if an exception were thrown and not caught, which would result in the file being left open (until the next garbage collection).

A try block doesn’t require a finally block associated with it; sometimes the code in a try block just doesn’t require any cleanup code. However, if you do have a finally block, it must appear after any and all catch blocks. A try block can have no more than one finally block associated with it.

When a thread reaches the end of the code contained in a finally block, the thread simply starts executing the statements immediately following the finally block. Remember that the code in the finally block is cleanup code. This code should execute only what is necessary to clean up opera- tions initiated in the try block. The code inside catch and finally blocks should be very short and should have a high likelihood of succeeding without itself throwing an exception. Usually the code in these blocks is just one or two lines of code.

It is always possible that exception-recovery code or cleanup code could fail and throw an excep- tion. Although possible, it is unlikely and if it does happen it usually means that there is something very wrong somewhere. Most likely some state has gotten corrupted somewhere. If an exception is inadvertently thrown within a catch or finally block, the world will not come to an end—the CLR’s exception mechanism will execute as though the exception were thrown after the finally block.

However, the CLR does not keep track of the first exception that was thrown in the corresponding try block (if any), and you will lose any and all information (such as the stack trace) available about the first exception. Probably (and hopefully), this new exception will not be handled by your code and the exception will turn into an unhandled exception. The CLR will then terminate your process, which is good because all the corrupted state will now be destroyed. This is much better than having your application continue to run with unpredictable results and possible security holes.

Personally, I think the C# team should have chosen different language keywords for the exception- handling mechanism. What programmers want to do is try to execute some piece of code. And then, if something fails, either recover from the failure and move on or compensate to undo some state change and continue to report the failure up to a caller. Programmers also want to have guaranteed cleanup no matter what happens.


The code on the left is what you have to write to make the C# compiler happy, but the code on the right is the way I prefer to think about it.

 

void Method() { void Method() { try { try {

... ...

} }

catch (XxxException) { handle (XxxException) {

... ...

} }

catch (YyyException) { handle (YyyException) {

... ...

} }

catch { compensate {

...; throw; ...

} }

finally { cleanup {

... ...

} }

} }

 

CLS and Non-CLS Exceptions

All programming languages for the CLR must support the throwing of Exception-derived ob- jects because the Common Language Specification (CLS) mandates this. However, the CLR actu- ally allows an instance of any type to be thrown, and some programming languages will allow code to throw non–CLS-compliant exception objects such as a String, Int32, or DateTime.

The C# compiler allows code to throw only Exception-derived objects, whereas code written in some other languages allow code to throw Exception-derived objects in addition to objects that are not derived from Exception.

Many programmers are not aware that the CLR allows any object to be thrown to report an exception. Most developers believe that only Exception-derived objects can be thrown. Prior to CLR 2.0, when programmers wrote catch blocks to catch exceptions, they were catching CLS-compliant exceptions only. If a C# method called a method written in another language, and that method threw a non–CLS-compliant exception, the C# code would not catch this exception at all, leading to some security vulnerabilities.

In CLR 2.0, Microsoft introduced a new RuntimeWrappedException class (defined in the System.Runtime.CompilerServices namespace). This class is derived from Exception, so it is a CLS-compliant exception type. The RuntimeWrappedException class contains a private field of type Object (which can be accessed by using RuntimeWrappedException’s Wrappe­ dException read-only property). In CLR 2.0, when a non–CLS-compliant exception is thrown, the CLR automatically constructs an instance of the RuntimeWrappedException class and initializes its private field to refer to the object that was actually thrown. In effect, the CLR now turns all non–CLS-compliant exceptions into CLS-compliant exceptions. Any code that now catches an Exception type will catch non–CLS-compliant exceptions, which fixes the potential security vulnerability problem.


Although the C# compiler allows developers to throw Exception-derived objects only, prior to C# 2.0, the C# compiler did allow developers to catch non–CLS-compliant exceptions by us- ing code like this.

private void SomeMethod() { try {

// Put code requiring graceful recovery and/or cleanup operations here...

}

catch (Exception e) {

// Before C# 2.0, this block catches CLS­compliant exceptions only

// Now, this block catches CLS­compliant & non–CLS­compliant exceptions throw; // Re­throws whatever got caught

}

catch {

// In all versions of C#, this block catches CLS­compliant

// & non–CLS­compliant exceptions throw; // Re­throws whatever got caught

}

}

Now, some developers were aware that the CLR supports both CLS-compliant and non– CLS-compliant exceptions, and these developers might have written the preceding two catch blocks in order to catch both kinds of exceptions. If the preceding code is recompiled for CLR

2.0 or later, the second catch block will never execute, and the C# compiler will indicate this by issuing a warning: CS1058: A previous catch clause already catches all excep­ tions. All non­exceptions thrown will be wrapped in a System.Runtime.

CompilerServices.RuntimeWrappedException.

 

There are two ways for developers to migrate code from a version of the .NET Framework prior to version 2.0. First, you can merge the code from the two catch blocks into a single catch block and delete one of the catch blocks. This is the recommended approach. Alter- natively, you can tell the CLR that the code in your assembly wants to play by the old rules. That is, tell the CLR that your catch (Exception) blocks should not catch an instance of the new RuntimeWrappedException class. And instead, the CLR should unwrap the non– CLS-compliant object and call your code only if you have a catch block that doesn’t specify any type at all. You tell the CLR that you want the old behavior by applying an instance of the RuntimeCompatibilityAttribute to your assembly like this.

using System.Runtime.CompilerServices; [assembly:RuntimeCompatibility(WrapNonExceptionThrows = false)]

 
 



Date: 2016-03-03; view: 682


<== previous page | next page ==>
Exceptions and State Management | Nbsp;   The System.Exception Class
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.01 sec.)