Handling Exceptions
One of the most fundamental and often most complex tasks of a developer is to design exception-handling procedures to allow an application to recover gracefully from an unexpected or disallowed condition.
The .NET Framework allows exception handling to be performed across languages, across multiple computers, and even across the Component Object Model (COM) and other legacy solutions. Within C#, the most common methods of exception handling involve the try, catch, and finally blocks, in addition to the throw statement.
TIP
try, catch, and finally blocks must be consecutive within code. You cannot have any intervening code separating these blocks when configuring event-handling solutions within your code.
try Block
When processing code that might generate an exception, the code can be placed within a try block, which, in turn, must be followed by one or more catch blocks or a finally block. Exceptions raised cause the CLR to search for the nearest try block (which can be nested within one another) and then pass control to the following catch block, or on to the finally block if no catch block is present in the code.
A try block in its most basic form looks like this:
try { //code that may cause exception }
Any normal code can be placed within the try block, including another try block or calls to methods that include try blocks of their own. When an exception occurs, the CLR finds the nearest try block and passes control to the following associated exception-handling blocks.
TIP
A try block must be followed by one or more catch blocks or by a finally block.
catch Blocks
After an exception is encountered within a try block, control is passed to the following catch blocks or to the finally block. catch blocks are evaluated in order until a match to the exception type is made. The catch blocks should be arranged in order from specific to more general types of the exception.
For example, a DivideByZeroException might match a DivideByZeroException, ArithmeticException, SystemException, or Exception catch block. In the case of multiple catch blocks, only the first matching catch block is executed. All other catch blocks are ignored. If no matching catch block can be found, the exception is passed back to the code that raised the exception and is considered an unhandled exception. This is discussed in greater detail later in this chapter.
NOTE
The default behavior for an unhandled exception is to cause the program to terminate with an error message.
Listing 3.1 shows an example of a try-catch block in use.
Listing 3.1 try-catch Block in Use
private void btnCalculate_Click(object sender, System.EventArgs e) { //put all the code that may require graceful error recovery //in a try block try { decimal decMiles = Convert.ToDecimal(txtMiles.Text); decimal decGallons = Convert.ToDecimal(txtGallons.Text); decimal decEfficiency = decMiles/decGallons; txtEfficiency.Text = String.Format("{0:n}", decEfficiency); } // try block should at least have one catch or a finally block // catch block should be in arranged in order of specific to // the generalized exceptions; // otherwise, compiler generates an error catch (FormatException fe) { string msg = String.Format( "Message: {0}\n Stack Trace:\n {1}", fe.Message, fe.StackTrace); MessageBox.Show(msg, fe.GetType().ToString()); } catch (DivideByZeroException dbze) { string msg = String.Format("Message: {0}\n Stack Trace:\n {1}", dbze.Message, dbze.StackTrace); MessageBox.Show(msg, dbze.GetType().ToString()); } //catches all CLS-compliant exceptions catch(Exception ex) { string msg = String.Format("Message: {0}\n Stack Trace:\n {1}", ex.Message, ex.StackTrace); MessageBox.Show(msg, ex.GetType().ToString()); } //catches all other exception including the //NON-CLS compliant exceptions catch { //just rethrow the exception to the caller throw; } }
Here, an exception raised within the code is thrown to the catch blocks to see if a match is made. When this code is compiled and run, an input zero value will be caught by the second catch block that catches exceptions of type DivideByZeroException, a non-numeric value will be caught by the first catch block that catches exceptions of type FormatException, or a very large numeric value will be caught by the third catch block that catches exceptions of type Exception from which the OverflowException derives. All languages that follow the Common Language Specification (CLS) generate an exception type that derives from System.Exception. Therefore, the catch block that catches exceptions of type Exception will be capable of catching all the exceptions generated by CLS-compliant languages.
The final catch block in the code example is included to handle exceptions generated by nonCLS-compliant languages. An unspecified catch block is the most general form of catch possible and should always be the last catch block if multiple catch blocks are present. The inclusion of the most general catch block prevents an unhandled exception. When the exceptions are caught, they will not cause the program to terminate.
NOTE
Visual C# .NET provides you with checked and unchecked keywords that can be used to enclose a block of statementsfor example, checked {a = c/d};or as an operator when you supply it parameters enclosed in parenthesesfor example, unchecked(c/d). The checked keyword enforces checking of an arithmetic operation for overflow exceptions. If constant values are involved, they are checked for overflow at compile time. The unchecked keyword suppresses overflow checking and does not raise any OverflowException. If any operation results in an overflow while using unchecked, its value is truncated and the result is returned.
finally Block
The finally block includes code that will run whether or not an exception is raised. This is a good location for cleanup code that closes open files or disconnects database connections to release held resources.
The following is an example of code that includes a finally block:
private void btnSave_Click(object sender, System.EventArgs e) { //a StreamWriter writes characters to a stream StreamWriter sw = null; try { sw = new StreamWriter(txtFileName.Text); //Attempt to write the textbox contents in a file foreach(string line in txtText.Lines) sw.WriteLine(line); //This line only executes if there were no exceptions so far MessageBox.Show("Contents written, without any exceptions"); } //catches all CLS-compliant exceptions catch(Exception ex) { string msg = String.Format("Message: {0}\n Stack Trace:\n {1}", ex.Message, ex.StackTrace); MessageBox.Show(msg, ex.GetType().ToString()); goto end; } // finally block is always executed to make sure that the // resources get closed whether or not the exception occurs. // Even if there is any goto statement in a catch or try block the // finally block is first executed before the control // goes to the goto label finally { if (sw != null) sw.Close(); MessageBox.Show( "finally block always executes whether or not exception occurs"); } end: MessageBox.Show("Control is at label: end"); }
The finally block can also be used in conjunction with a try block, without any catch blocks. Exceptions raised are returned as unhandled exceptions in this case. An example of the simplest form of this follows:
try { //Write code to allocate some resources } finally { //Write code to Dispose all allocated resources }
This usage ensures that allocated resources are properly disposed whether the exception occurs. In fact, C# provides a using statement that does the same job, but with less code. A typical use of the using statement is as follows:
// Write code to allocate some resource. // List the allocate resources in a comma-separated list // inside the parentheses, with the following using block using(...) { // use the allocated resource } // Here, the Dispose method is called for all the objects referenced // in the parentheses of the using statement. // There is no need to write any additional code
It is possible to programmatically throw exceptions within the finally block using the throw statement, but this is a bad practice because there might already be unhandled exceptions waiting to be handled.
TIP
try, catch, and finally blocks must be consecutive within code. You cannot have any intervening code separating these blocks when configuring event-handling solutions within your code.
The throw Statement
The throw statement can be used to explicitly throw an exception within your code. This can be used to rethrow a caught exception after performing a task such as generating an event log entry or sending an email notification of the exception. Or, it can be used to raise a custom exception explicitly defined within your code.
NOTE
Throwing an exception is a costly operation and, if carelessly used, can potentially slow down your application. You should throw exceptions with caution and only when essential. You should avoid throwing exceptions for regular control transfer.
An example of the throw statement being used to rethrow a caught exception looks like this in its simplest form:
catch(Exception e) { //TODO: Add code to create an entry in event log throw; }
You can also use the throw statement to throw explicitly created exceptions (that contain a custom error message), as shown here:
string strMessage = "EndDate should be greater than the StartDate"; ArgumentOutOfRangeException newException = new ArgumentOutOfRangeException(strMessage); throw newException;
Creating Custom Exceptions
The .NET Framework CLR includes many standard exception types, but occasionally it is necessary or desirable to create a custom exception to address a specific need within your code. This should be done only if there is not already an existing exception class that satisfies your requirements.
While creating a new custom exception class, Microsoft recommends that you consider the following guidelines:
Derive all programmer-defined custom exception classes from the System.ApplicationException class.
End the name of your custom exception class with the word Exception (for example, MyOwnCustomException).
Implement three constructors with the signatures shown in the following code:
public class MyOwnCustomException : ApplicationException { // Default constructor public MyOwnCustomException () { } // Constructor accepting a single string message public MyOwnCustomException (string message) : base(message) { } // Constructor accepting a string message and an inner exception // that will be wrapped by this custom exception class public MyOwnCustomException(string message, Exception inner) : base(message, inner) { } }