Perform Exception Handling in .NET Exceptionally

An exception is an error condition or unexpected behavior that occurs within an application. Exceptions can occur from within classes in the Microsoft .NET Framework or other classes you use. You also can raise them in your own code. Exceptions can result from various conditions, such as the following:

  • Attempting to access a null object's property or method
  • Indexing into an array location that is out of bounds
  • Trying to access an unavailable system resource
  • Executing an invalid SQL statement against a database

When such conditions occur, the offending code throws (or raises) an exception. The exception is passed back up the calling stack until it is handled (or caught), or the application terminates due to the unhandled exception.

Exception handling is a structured form of error handling that differs from the unstructured error handling many traditional Visual Basic developers are accustomed to using. In unstructured error handling such as the On Error Goto of old, the block of code has the same error handler regardless of the error that occurs. In structured error handling, any number of different error filtering conditions can be set in place for a block of code. Structured error handling allows you to dictate proper programming to those who use your code. In the past, an error could occur from a call to your code and you would have no way to ensure that the calling code had to account for an unknown condition. Through exceptions, your method can return an exception and force the caller to account for the possible exceptions in some way.

Existing Exceptions

The .NET Framework contains a number of exceptions that all derive from the base class System.Exception. It is easier to rely on existing exceptions where possible, and create new types only when necessary. The following list contains some of the more common exceptions:

  • ArgumentException—invalid argument of some sort passed to a method
  • ArgumentNullException—used by methods that don't allow arguments to be null
  • DivideByZeroException—attempt to divide by zero
  • IndexOutOfRangeException—improper index into an array
  • InvalidCastException—results from an invalid cast or explicit conversion attempt
  • InvalidOperationException—object is in an invalid state with respect to method call
  • NullReferenceException—when a null object is referenced

Using Exceptions

Structured exception handling has three main parts. The code that you are attempting to execute is contained within a try block. The exception(s) are captured for handling through the use of trailing catch blocks. Different types of exceptions are handled through consecutive catch statements. Each catch statement handles a different exception. The ordering of the catch statements should flow from most specific to most general. An optional finally block can follow a sequence of catch blocks. No matter what happens with the code contained within the try block, the code contained within the finally block always executes. This allows you the option to clean up after the try block (for example, when a database connection was opened and needs to always be closed after use).

Exception Handling Sample Code

The following code represents a traditional try...catch...finally sequence:

try
{
   // Some code here, such as array access, method call, or
   // accessing a database
}
catch( IndexOutOfRangeException outRangeEx )
{
   // Specific exception handler
}
catch( InvalidOperationException invalidOpEx )
{
   // Specific exception handler
}
...
finally
{
   // Clean up from try, such as closing database connection
}

The following sample code represents a try...finally statement:

try
{
   // Some code here, such as array access, method call, or
   // accessing a database
}
finally
{
   // Clean up from try, such as closing database connection
}

This statement ensures that the finally block, which is typically used to clean up whatever occurred in the try block, executes, but it allows the exception to be propagated back up the call stack. In addition—although not necessarily recommended—you can have a try...catch without specifying a specific exception to catch:

try
{
   // Some code here, such as array access, method call, or
   // accessing a database
}
catch
{
   // Generic exception handler
}

Perform Exception Handling in .NET Exceptionally

Creating Custom Exceptions

As a rule of thumb, you should always try to use the pre-defined exception types. However, sometimes creating your own custom exceptions has its advantages. Microsoft recommends deriving custom exceptions from the ApplicationException class rather than the System.Exception class. This allows you to differentiate between the exceptions your application throws and those that originate from within the runtime system.

Custom Exception Sample Code

A useful example of a custom exception (right or wrong) is one you'd use in a data access layer. When an exception occurs, including the offending SQL statement within the exception is typically useful to ensure that the exact SQL statement can be debugged. As an example, the following code creates a custom exception that allows you to include the SQL statement being executed. All you have to do is extend the ApplicationException base class to include your additional property:

using System;
using System.Runtime.Serialization;

namespace CodeGuru.ExceptionHandling
{
/// <summary>
/// The exception that is thrown when the database layer returns a
/// or error.
/// </summary>
[Serializable]
public class DatabaseException : ApplicationException
{
   private string _CommandText = "";
   /// <summary>Get the SQL command text that caused the
   /// exception</summary>
   public string CommandText
   {
      get { return this._CommandText; }
   }


   /// <summary>
   /// Constructor
   /// </summary>
   public DatabaseException()
   {
   }


   /// <summary>
   /// Constructor
   /// </summary>
   /// <param name="Message">Message that describes the current
   /// exception.</param>
   public DatabaseException(string Message) : base(Message)
   {
   }


   /// <summary>
   /// Constructor
   /// </summary>
   /// <param name="Message">Message that describes the current
   /// exception.</param>
   /// <param name="InnerException">System.Exception event that
   /// caused the current exception.</param>
   public DatabaseException(string Message,
                            Exception InnerException) :
                            base(Message, InnerException)
   {
   }


   /// <summary>
   /// Constructor
   /// </summary>
   /// <param name="Message">Message that describes the current
   /// exception.</param>
   /// <param name="SqlCommandText">SQL command text that caused
   /// the exception.</param>
   public DatabaseException(string Message, string SqlCommandText) :
                           base(Message)
   {
      this._CommandText = SqlCommandText;
   }


   /// <summary>
   /// Constructor
   /// </summary>
   /// <param name="Message">Message that describes the current
   /// exception.</param>
   /// <param name="SqlCommandText">SQL command text that caused
   /// the exception.</param>
   /// <param name="InnerException">System.Exception event that
   /// caused the current exception.</param>
   public DatabaseException(string Message, string SqlCommandText,
                            Exception InnerException) :
                           base(Message, InnerException)
   {
      this._CommandText = SqlCommandText;
   }


   /// <summary>
   /// Constructor
   /// </summary>
   /// <param name="SerializationInfo">Holds the serialized object
   /// data about the exception being thrown.</param>
   /// <param name="StreamingContext">Contains contextual
   /// information about the source or destination.</param>
   protected DatabaseException(SerializationInfo SerializationInfo,
                               StreamingContext StreamingContext) :
                              base(SerializationInfo,
                                   StreamingContext)
   {
   }
}
}

Now that you've defined your custom exception class, you can use it. The following sample code tries to connect to a database (SQL Server with the Northwind database) and execute a SQL statement:

SqlConnection connection = new SqlConnection(
   "Server=localhost;Database=Northwind;Integrated Security=false;
    User Id=sa;Password=;");
System.Text.StringBuilder sqlStatement = 
   new System.Text.StringBuilder("SELECT * FROM FakeTable");

try
{
   SqlCommand dbCommand = new SqlCommand(sqlStatement.ToString(),
                                         connection);
   dbCommand.CommandType = System.Data.CommandType.Text;
   SqlDataAdapter adapter = new SqlDataAdapter(dbCommand);
   System.Data.DataSet data = new System.Data.DataSet();
   adapter.Fill(data);
}
catch( SqlException exception )
{
   throw new DatabaseException(
      exception.Message, sqlStatement.ToString(), exception);
}
finally
{
   if( connection != null ) connection.Dispose();
}

The SQL, or lack of a database, will cause an exception and provide an opportunity to catch it and wrap the exception in your custom DatabaseConnection. This demonstrates how you can use exceptions in your data access layer and wrap them in a custom exception.

Following Best Practices and Establishing Standards

As with many aspects of programming, you should establish and follow a set of coding standards around exceptions. Establishing standards helps maximize performance and provides the most value from exception usage. Two of the more important items to focus on are when to use exceptions and the method for trapping them.

When using exceptions, you should establish a policy for when to throw an exception, what to put into the exception, and when to create a custom exception. Exceptions have performance implications because you incur overhead when creating and handling them. Thus, if it you can check whether a particular exception state would occur prior to executing the code, you should check for the condition first rather than allowing an exception to occur—especially if you're executing in a loop.

For example, when you want to open a file, you can avoid the FileNotFoundException by using the File.Exists method to determine whether or not the file exists prior to opening the file. Along the same lines, you should avoid using exceptions for common or expected error conditions. Rather than throwing an exception, you should consider returning null or some other sort of error condition. As an example, rather than throwing an exception, String.IndexOf returns -1 if the desired match string does not exist because it is fairly common for the match string not to exist in the source string. An example of where this isn't followed and probably should be is when it comes to checking whether a particular object is a number, which is a common step in many applications that accept input. The only way to check is to try and convert the object to a number with one of the methods in System.Convert, which throws an exception if the object is not a number. Hopefully, Microsoft will address this in future versions of the .NET Framework.

Another important item you should be consistent about is the type of information you put into an exception. All too often, programmers put cryptic or unhelpful information into exceptions. Messages such as "An error has occurred." are not all that helpful. A common tendency is to catch an exception when it occurs and then throw a new exception in its place. If you don't include enough relevant information in your exception, it will be hard for other programmers to determine what caused it or what to do about it. Always test out the relevance of the contained information by logging it to wherever you trap your application logs. The litmus test is whether or not the log provides enough information to diagnose and debug the exception. Additionally, if you don't have anything relevant to add to the exception thrown, nor are you planning to recover from it in the area where it is being trapped, then why trap and re-throw it in the first place? You're better off just allowing the exception to pass back up the calling stack unchanged.

Future Columns

The topic of the next column is yet to be determined. If you have something in particular that you would like to see explained here, you could reach me at mstrawmyer@crowechizek.com.



About the Author

Mark Strawmyer

Mark Strawmyer is a Senior Architect of .NET applications for large and mid-size organizations. He specializes in architecture, design and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the fifth year in a row. You can reach Mark at mark.strawmyer@crowehorwath.com.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

  • On-demand Event Event Date: March 27, 2014 Teams need to deliver quality software faster and need integrated agile planning, task tracking, source control, auto deploy with continuous builds and a configurable process to adapt to the way you work. Rational Team Concert and DevOps Services (JazzHub) have everything you need to build great software, integrated seamlessly together right out of the box or available immediately in the cloud. And with the Rational Team Concert Client, you can connect your …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds