Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Recovering Gracefully from an Exception

Sometimes you call a method knowing in advance some of the exceptions that the method might throw. Because you expect these exceptions, you might want to have some code that allows your application to recover gracefully from the situation and continue running. Here’s an example in pseudocode.

 

public String CalculateSpreadsheetCell(Int32 row, Int32 column) { String result;

try {

result = /* Code to calculate value of a spreadsheet's cell */

}

catch (DivideByZeroException) {

result = "Can't show value: Divide by zero";

}

catch (OverflowException) {

result = "Can't show value: Too big";

}

return result;

}

 

This pseudocode calculates the contents of a cell in a spreadsheet and returns a string representing the value to the caller so that the caller can display the string in the application’s window. However,

a cell’s contents might be the result of dividing one cell by another cell. If the cell containing the de- nominator contains 0, the CLR will throw a DivideByZeroException object. In this case, the method catches this specific exception and returns a special string that will be displayed to the user. Similarly, a cell’s contents might be the result of multiplying one cell by another. If the multiplied value doesn’t fit in the number of bits allowed, the CLR will throw an OverflowException object, and again, a special string will be displayed to the user.

When you catch specific exceptions, fully understand the circumstances that cause the exception to be thrown, and know what exception types are derived from the exception type you’re catching. Don’t catch and handle System.Exception (without re-throwing) because it’s not feasible for you to know all of the possible exceptions that could be thrown within your try block (especially if you consider the OutOfMemoryException or the StackOverflowException, to name two).


Backing Out of a Partially Completed Operation When an Unrecoverable Exception Occurs—Maintaining State

Usually, methods call several other methods to perform a single abstract operation. Some of the in- dividual methods might complete successfully, and some might not. For example, let’s say that you’re serializing a set of objects to a disk file. After serializing 10 objects, an exception is thrown. (Perhaps the disk is full or the next object to be serialized isn’t marked with the Serializable custom at- tribute.) At this point, the exception should filter up to the caller, but what about the state of the disk file? The file is now corrupted because it contains a partially serialized object graph. It would be great if the application could back out of the partially completed operation so that the file would be in the state it was in before any objects were serialized into it. The following code demonstrates the correct way to implement this.

 

public void SerializeObjectGraph(FileStream fs, IFormatter formatter, Object rootObj) {

 

// Save the current position of the file. Int64 beforeSerialization = fs.Position;



 

try {

// Attempt to serialize the object graph to the file. formatter.Serialize(fs, rootObj);

}

catch { // Catch any and all exceptions.

// If ANYTHING goes wrong, reset the file back to a good state. fs.Position = beforeSerialization;

 

// Truncate the file. fs.SetLength(fs.Position);

 

// NOTE: The preceding code isn't in a finally block because

// the stream should be reset only when serialization fails.

 

// Let the caller(s) know what happened by re­throwing the SAME exception. throw;

}

}

 

To properly back out of the partially completed operation, write code that catches all exceptions. Yes, catch all exceptions here because you don’t care what kind of error occurred; you need to put your data structures back into a consistent state. After you’ve caught and handled the exception, don’t swallow it—let the caller know that the exception occurred. You do this by re-throwing the same exception. In fact, C# and many other languages make this easy. Just use C#’s throw keyword without specifying anything after throw, as shown in the previous code.

Notice that the catch block in the previous example doesn’t specify any exception type because I want to catch any and all exceptions. In addition, the code in the catch block doesn’t need to know exactly what kind of exception was thrown, just that something went wrong. Fortunately, C# lets me do this easily just by not specifying any exception type and by making the throw statement re-throw whatever object is caught.


Hiding an Implementation Detail to Maintain a “Contract”

In some situations, you might find it useful to catch one exception and re-throw a different exception. The only reason to do this is to maintain the meaning of a method’s contract. Also, the new exception type that you throw should be a specific exception (an exception that’s not used as the base type of any other exception type). Imagine a PhoneBook type that defines a method that looks up a phone number from a name, as shown in the following pseudocode.

 

internal sealed class PhoneBook {

private String m_pathname; // path name of file containing the address book

 

// Other methods go here.

 

public String GetPhoneNumber(String name) { String phone;

FileStream fs = null; try {

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

// Code to read from fs until name is found goes here phone = /* the phone # found */

}

catch (FileNotFoundException e) {

// Throw a different exception containing the name, and

// set the originating exception as the inner exception. throw new NameNotFoundException(name, e);

}

catch (IOException e) {

// Throw a different exception containing the name, and

// set the originating exception as the inner exception. throw new NameNotFoundException(name, e);

}

finally {

if (fs != null) fs.Close();

}

return phone;

}

}

 

The phone book data is obtained from a file (versus a network connection or database). However, the user of the PhoneBook type doesn’t know this because this is an implementation detail that could change in the future. So if the file isn’t found or can’t be read for any reason, the caller would see a FileNotFoundException or IOException, which wouldn’t be anticipated. In other words, the file’s existence and ability to be read is not part of the method’s implied contract: there is no way the caller could have guessed this. So the GetPhoneNumber method catches these two exception types and throws a new NameNotFoundException.

When using this technique, you should catch specific exceptions that you fully understand the cir- cumstances that cause the exception to be thrown. And, you should also know what exception types are derived from the exception type you’re catching.


Throwing an exception still lets the caller know that the method cannot complete its task, and the NameNotFoundException type gives the caller an abstracted view as to why. Setting the inner exception to FileNotFoundException or IOException is important so that the real cause of the exception isn’t lost. Besides, knowing what caused the exception could be useful to the developer of the PhoneBook type and possibly to a developer using the PhoneBook type.

       
   
 
 

 

Now let’s say that the PhoneBook type was implemented a little differently. Assume that the type offers a public PhoneBookPathname property that allows the user to set or get the path name of the file in which to look up a phone number. Because the user is aware of the fact that the phone book data comes from a file, I would modify the GetPhoneNumber method so that it doesn’t catch any ex- ceptions; instead, I let whatever exception is thrown propagate out of the method. Note that I’m not changing any parameters of the GetPhoneNumber method, but I am changing how it’s abstracted to users of the PhoneBook type. Users now expect a path to be part of the PhoneBook’s contract.

Sometimes developers catch one exception and throw a new exception in order to add additional data or context to an exception. However, if this is all you want to do, you should just catch the excep- tion type you want, add data to the exception object’s Data property collection, and then re-throw the same exception object.

 

private static void SomeMethod(String filename) { try {

// Do whatevere here...

}

catch (IOException e) {

// Add the filename to the IOException object e.Data.Add("Filename", filename);

 

throw; // re­throw the same exception object that now has additional data in it

}

}

Here is a good use of this technique: when a type constructor throws an exception that is not caught within the type constructor method, the CLR internally catches that exception and throws a new TypeInitializationException instead. This is useful because the CLR emits code within your methods to implicitly call type constructors.6 If the type constructor threw a DivideByZeroException,

 
 

6 For more information about this, see the “Type Constructors” section in Chapter 8, “Methods.”


your code might try to catch it and recover from it but you didn’t even know you were invoking the type constructor. So the CLR converts the DivideByZeroException into a TypeInitialization­ Exception so that you know clearly that the exception occurred due to a type constructor failing; the problem wasn’t with your code.

On the other hand, here is a bad use of this technique: when you invoke a method via reflec- tion, the CLR internally catches any exception thrown by the method and converts it to a Target­ InvocationException. This is incredibly annoying because you must now catch the Target­ InvocationException object and look at its InnerException property to discern the real reason for the failure. In fact, when using reflection, it is common to see code that looks like this.

 

private static void Reflection(Object o) { try {

// Invoke a DoSomething method on this object var mi = o.GetType().GetMethod("DoSomething");

mi.Invoke(o, null); // The DoSomething method might throw an exception

}

catch (System.Reflection.TargetInvocationException e) {

// The CLR converts reflection­produced exceptions to TargetInvocationException throw e.InnerException; // Re­throw what was originally thrown

}

}

 

I have good news though: if you use C#’s dynamic primitive type (discussed in Chapter 5, “Primi- tive, Reference, and Value Types”) to invoke a member, the compiler-generated code does not catch any and all exceptions and throw a TargetInvocationException object; the originally thrown exception object simply walks up the stack. For many developers, this is a good reason to prefer using C#’s dynamic primitive type rather than reflection.

 

 


Date: 2016-03-03; view: 724


<== previous page | next page ==>
Don’tCatch Everything | Nbsp;   Unhandled Exceptions
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.009 sec.)