Introduction


This chapter contains recipes covering the exception-handling mechanism, including the try, catch, and finally blocks. Along with these recipes are others covering the mechanisms used to throw exceptions manually from within your code. The final types of recipes include those dealing with the Exception classes and their uses, as well as subclassing them to create new types of exceptions.

Often the design and implementation of exception handling is performed later in the development cycle. But with the power and complexities of C# exception handling, you need to plan and even implement your exception-handling scheme much earlier. Doing so will increase the reliability and robustness of your code while minimizing the impact of adding exception handling after most or all of the application is coded.

Exception handling in C# is very flexible. It allows you to choose a fine-or coarse-grained approach to error handling and any level between. This means that you can add exception handling around any individual line of code (the fine-grained approach) or around a method that calls many other methods (the coarse-grained approach), or you can use a mix of the two, with mainly a coarse-grained approach and a more fine-grained approach in specific critical areas of the code. When using a fine-grained approach, you can intercept specific exceptions that might be thrown from just a few lines of code. The following method sets an object's property to a numeric value using fine-grained exception handling:

 protected void SetValue(object value) {     try     {         myObj.Property1 = value;     }     catch (Exception e)     {         // Handle potential exceptions arising from this call here.     } } 

Consequently, this approach can add a lot of extra baggage to your code if used throughout your application. This fine-grained approach to exception handling should be used when you have a single line or just a few lines of code and you need to handle that exception in a specific manner. If you do not have specific handling for errors at that level, you should let the exception bubble up the stack. For example, using the previous SetValue method, you may have to inform the user that an exception occurred and provide a chance to try the action again. If a method exists on myObj that needs to be called whenever an exception is thrown by one of its methods, you should make sure that this method is called at the appropriate time.

Coarse-grained exception handling is quite the opposite; it uses fewer try/catch or try/catch/finally blocks. One example would be to place a try/catch block around all of the code in every public method in an application or component. Doing this allows exceptions to be handled at the highest level in your code. If an exception is thrown at any location in your code, it will be bubbled up the call stack until a catch block is found that can handle it. If try/catch blocks are placed on all public methods, then all exceptions will be bubbled up to these methods and handled. This allows for much less exception-handling code to be written, but your ability to handle specific exceptions that may occur in particular areas of your code is diminished. You must determine how best to add exception-handling code to your application. This means applying the right balance of fine- and coarse-grained exception handling in your application.

C# allows catch blocks to be written without any parameters. An example of this is shown here:

 public void CallCOMMethod( ) {     try     {         // Call a method on a COM object.         myCOMObj.Method1( );     }     catch     {         // Handle potential exceptions arising from this call here.     } } 

The catch with no parameters is a holdover from C++, where exception objects did not have to be derived from the Exception class. Writing a catch clause in this manner in C++ allows any type of object thrown as an exception to be caught. However, in C#, only objects derived from the Exception base class may be thrown as an exception. Using the catch block with no parameters allows all exceptions to be caught, but you lose the ability to view the exception and its information. A catch block written in this manner:

 catch {     // NOT Able to write the following line of code     //Console.WriteLine(e.ToString); } 

is equivalent to this:

 catch (Exception e) {     // Able to write the following line of code     Console.WriteLine(e.ToString); } 

except that the Exception object can now be accessed.

If you are catching an exception that was thrown from C++, it may not have originally derived from System.Exception, as C++ can throw integers, strings, and custom exception classes as exceptions. Similarly, the Win32 API function RaiseError allows for raising an exception as well, but ultimately all of these types are mapped by the CLR back to an instance of a class derived from System.Exception.


Avoid writing a catch block without any parameters. Doing so will prevent you from accessing the actual Exception object that was thrown.

When catching exceptions in a catch block, you should determine up front when exceptions need to be rethrown, when exceptions need to be wrapped in an outer exception and thrown, and when exceptions should be handled immediately and not rethrown.

Wrapping an exception in an outer exception is a good practice when the original exception would not make sense to the caller. When wrapping an exception in an outer exception, you need to determine what exception is most appropriate to wrap the caught exception. As a rule of thumb, the wrapping exception should always aid in tracking down the original problem by not obscuring the original exception with an unrelated or vague wrapping exception.

Another useful practice when catching exceptions is to provide catch blocks to handle specific exceptions in your code. When using specific catch blocks, consider adding a generic catch block that handles all other exceptions (Exception). This will ensure that all other exceptions are handled at some point in your code. Also, remember that base class exceptionswhen used in a catch blockcatch not only that type but also all of its subclasses.

The following code uses specific catch blocks to handle different exceptions in the appropriate manner:

 public void CallCOMMethod( ) {     try     {         // Call a method on a COM object.         myCOMObj.Method1( );     }     catch (System.Runtime.InteropServices.ExternalException exte)     {         // Handle potential COM exceptions arising from this call here.     }     catch (InvalidOperationException ae)     {         // Handle any potential method calls to the COM object which are         // not valid in its current state.     } } 

In this code, ExternalException and its derivatives are handled differently than InvalidOperationException and its derivatives. If any other types of exceptions are thrown from the myCOMObj.Method1, they are not handled here, but are bubbled up until a valid catch block is found. If no valid catch block is found, the exception is considered unhandled and the application terminates.

At times, cleanup code must be executed regardless of whether an exception is thrown. Any object must be placed in a stable known state when an exception is thrown. In these situations when code must be executed, use a finally block. The following code has been modified (see boldface lines) to use a finally block:

 public void CallCOMMethod( ) {     try     {         // Call a method on a COM object.         myCOMObj.Method1( );     }     catch (System.Runtime.InteropServices.ExternalException exte)     {         // Handle potential COM exceptions arising from this call here.     }     finally     {         // Clean up and free any resources here.         // For example, there could be a method on myCOMObj to allow us to clean         // up after using the Method1 method.     } } 

The finally block will always execute, no matter what happens in the try and catch blocks. The finally block executes even if a return, break, or continue statement is executed in the TRy or catch blocks or if a goto is used to jump out of the exception handler. This allows for a reliable method of cleaning up after the TRy (and possibly catch) block code executes. The finally block is also very useful for final resource cleanup when no catch blocks are specified. This pattern would be used if the code being written can't handle exceptions from calls it is making but wants to make sure that resources it uses are cleaned up properly before moving up the stack. The following example makes sure that the SqlConnection and SqlCommand are cleaned up properly in the finally block:

 public void RunCommand( string connection, string command ) {     SqlConnection sqlConn = null;     SqlCommand sqlComm = null;     try     {         sqlConn = new SqlConnection(connection);             sqlComm = new SqlCommand(command, sqlConn);         sqlConn.Open();             sqlComm.ExecuteNonQuery();     }     finally     {         if (null != sqlComm);                 sqlComm.Dispose();             if (null != sqlConn)                 sqlConn.Dispose();     } } 

When determining how to structure exception handling in your application or component, consider doing the following:

  • Use a single try-catch or try-catch-finally exception handler at locations higher up in your code. These exception handlers can be considered coarse-grained.

  • Code farther down the call stack should contain try-finally exception handlers. These exception handlers can be considered fine-grained.

The fine-grained try-finally exception handlers allow for better control over cleaning up after an exception occurs. The exception is then bubbled up to the coarser-grained try-catch or try-catch-finally exception handler. This technique allows for a more centralized scheme of exception handling and minimizes the code that you have to write to handle exceptions.

To improve performance, you should handle the case when an exception could be thrown, rather than catch the exception after it is thrown, if you know the code will be run in a single-threaded environment. If the code will run on multiple threads, there is still the potential that the initial check could succeed but the object value change (perhaps to null) in another thread before the actions following the check can be taken.

For example, in a single-threaded environment, if a method has a good chance of returning a null value, you should test the returned value for null before that value is used, as opposed to using a TRy-catch block and allowing the NullReferenceException to be thrown. Remember that throwing an exception has a negative impact on performance, whereas exception-handling code has a minimal impact on performance, as long as an exception is not thrown.

To illustrate this, here is a method that uses exception-handling code to process the NullReferenceException:

 public void SomeMethod( ) {     try     {         Stream s = GetAnyAvailableStream( );         Console.WriteLine("This stream has a length of " + s.Length);     }      catch (Exception e)     {         // Handle a null stream here.     } } 

Here is the method implemented to use an if-else conditional instead:

 public void SomeMethod( ) {     Stream s = GetAnyAvailableStream( );          if (s != null)     {         Console.WriteLine("This stream has a length of " + s.Length);     }     else     {         // Handle a null stream here.     } } 

Additionally, you should also make sure that this stream is closed by using the finally block in the following manner:

 public void SomeMethod( ) {     Stream s = null;     try     {         s = GetAnyAvailableStream( );                  if (s != null)         {             Console.WriteLine("This stream has a length of " + s.Length);         }         else         {             // Handle a null stream here.         }     }     finally     {         if (s != null)         {             s.Close( );         }     } } 

The finally block contains the method call that will close the stream, ensuring that there is no data loss.

Consider throwing exceptions instead of returning error codes. With well-placed exception-handling code, you should not have to rely on methods that return error codes such as a Boolean TRue-false to correctly handle errors, which makes for much cleaner code. Another benefit is that you do not have to look up any values for the error codes to understand the code. However, the biggest advantage is that when an exceptional situation arises, you cannot just ignore it as you can with error codes.

This technique is especially useful when writing a managed C# component that is called by one or more COM objects. Throwing an exception is much cleaner and easier to read than returning an hrESULT. The managed wrapper that the runtime creates for your managed object will convert the hrESULT to the nearest equivalent managed exception type.

Throw specific exceptions, not general ones. For example, throw an ArgumentNullException instead of an ArgumentException, which is the base class of ArgumentNullException. THRowing an ArgumentException just tells you that there was a problem with a parameter value to a method. Throwing an ArgumentNullException tells you more specifically what the problem with the parameter really is. Another potential problem is that a more general exception may not be caught if the catcher of the exception is looking for a more specific type derived from the thrown exception.

The FCL provides several exception types that you will find very useful to throw in your own code. Many of these exceptions are listed here with a definition of where and when they should be thrown:

  • Throw an InvalidOperationException in a property, indexer, or method when it is called with the object in an inappropriate state. This state could be caused by calling an indexer on an object that has not yet been initialized or calling methods out of sequence.

  • Throw ArgumentException if invalid parameters are passed into a method, property, or indexer. The ArgumentNullException, ArgumentOutOfRangeException, and InvalidEnumArgumentException are three subclasses of the ArgumentException class. It is more appropriate to throw one of these subclassed exceptions since they are more indicative of the root cause of the problem. The ArgumentNullException indicates that a parameter was passed in as null and that this parameter cannot be null under any circumstance. The ArgumentOutOfRangeException indicates that an argument was passed in that was outside of a valid acceptable range. This exception is used mainly with numeric values. The InvalidEnumArgumentException indicates that an enumeration value was passed in that does not exist in that enumeration type.

  • Throw a FormatException when an invalid formatting parameter is passed in as a parameter to a method. This technique is mainly used when overriding/over-loading methods such as ToString that can accept formatting strings, as well as in the parse methods on the various numeric types.

  • Throw ObjectDisposedException when a property, indexer, or method is called on an object that has already been disposed.

  • Many exceptions that derive from the SystemException class, such as NullReferenceException, ExecutionEngineException, StackOverflowException, OutOfMemoryException, and IndexOutOfRangeException are thrown only by the CLR and should not be explicitly thrown with the tHRow keyword in your code.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net