Home > Articles

Handling Exceptions

  • Print
  • + Share This
This chapter is from the book

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 non–CLS-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 statements—for example, checked {a = c/d};—or as an operator when you supply it parameters enclosed in parentheses—for 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)
  {
  }
}
  • + Share This
  • 🔖 Save To Your Account