C# Programming/Exceptions

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Introduction[edit | edit source]

Software Programmers write code to perform some desired actions. But every software may fail to perform its desired actions for internal or external reasons. The exception handling system in the C# language allows the programmer to handle errors or anomalous situations in a structured manner that allows the programmer to separate the normal flow of the code from error-handling logic.

An exception can represent a variety of abnormal conditions that arise during the execution of the software. Such conditions can be internal or externally caused. External conditions of execution failures include, for example, network failures in connecting to a remote component, inadequate rights in using a file/system resource, out of memory exceptions or exceptions thrown by a web service etc. These are mainly due to failures thrown by environment components on which our application depends, e.g. the operating system, the .NET runtime or external applications or components. Internal failures may be due to software defects, designed functional failures (failures required as per business rules), propagated external failures, e.g. a null object reference detected by the runtime system, an invalid input string entered by a user and detected by application code, or a user requesting to withdraw a larger amount than the account balance (business rule).

Code that detects an error condition is said to throw an exception and code that handles the error is said to catch the exception. An exception in C# is an object that encapsulates various pieces of information about the error that occurred, such as the stack trace at the point of the exception and a descriptive error message. All exception objects are instantiations of the System.Exception class or a child class of it. There are many exception classes defined in the .NET Framework used for various purposes. Programmers may also define their own class inheriting from System.Exception or some other appropriate exception class from the .NET Framework.

Microsoft recommendations prior to version 2.0 recommended that a developer's exception classes should inherit from the ApplicationException exception class. After 2.0 was released, this recommendation was made obsolete and users' exception classes should now inherit from the Exception class[1].

Overview[edit | edit source]

There are three code definitions for exception handling. These are:

  • try/catch - Do something and catch an error, if it should occur.
  • try/catch/finally - Do something and catch an error if it should occur, but always do the finally.
  • try/finally - Do something, but always do the finally. Any exception that occurs, will be thrown after finally.

Exceptions are caught in the order from most specific to least specific. So for example, if you try and access a file that does not exist, the CLR would look for exception handlers in the following order:

  • FileNotFoundException
  • IOException (base class of FileNotFoundException)
  • SystemException (base class of IOException)
  • Exception (base class of SystemException)

If the exception being thrown does not derive from or is not in the list of exceptions to catch, it is thrown up the call stack.

Below are some examples of the different types of exceptions

Examples[edit | edit source]

try/catch[edit | edit source]

The try/catch performs an operation and should an error occur, will transfer control to the catch block, should there be a valid section to be caught by:

class ExceptionTest
{
     public static void Main(string[] args)
     {
          try
          {
               Console.WriteLine(args[0]);
               Console.WriteLine(args[1]);
               Console.WriteLine(args[2]);
               Console.WriteLine(args[3]);
               Console.WriteLine(args[4]);
          }
          catch (ArgumentOutOfRangeException e)
          {
               Console.WriteLine(e.Message);
          }
     }
}

Here is an example with multiple catches:

class ExceptionTest
{
     public static void Main(string[] args)
     {
          try
          {
               string fileContents = new StreamReader(@"C:\log.txt").ReadToEnd();
          }
          catch (UnauthorizedAccessException e) // Access problems
          {
               Console.WriteLine(e.Message);
          }
          catch (FileNotFoundException e)       // File does not exist
          {
               Console.WriteLine(e.Message);
          }
          catch (IOException e)                // Some other IO problem.
          {
               Console.WriteLine(e.Message);
          }
     }
}

In all catch statements you may omit the type of exception and the exception variable name:

try
{
    int number = 1/0;
}
catch (DivideByZeroException)
{
    // DivideByZeroException
}
catch
{
    // some other exception
}

try/catch/finally[edit | edit source]

Catching the problem is a good idea, but it can sometimes leave your program in an invalid state. For example, if you open a connection to a database, an error occurs and you throw an exception. Where would you close the connection? In both the try AND exception blocks? Well, problems may occur before the close is carried out.

Therefore, the finally statement allows you to cater for the "in all cases do this" circumstance. See the example below:

using System;
class ExceptionTest
{
     public static void Main(string[] args)
     {
          SqlConnection sqlConn = null;

          try
          {
              sqlConn = new SqlConnection ( /*Connection here*/ );
              sqlConn.Open();
 
              // Various DB things
        
              // Notice you do not need to explicitly close the connection, as .Dispose() does this for you.
          }
          catch (SqlException e)
          {
               Console.WriteLine(e.Message);
          }
          finally
          {
               if (sqlConn != null && sqlConn.State != ConnectionState.Closed)
               {
                   sqlConn.Dispose();
               }
          }
     }
}

Second Example

using System;
public class excepation
{
	public double num1, num2,result;
			
	public void add()
	{
		try
		{
			Console.WriteLine("enter your number");
			num1 = Convert.ToInt32(Console.ReadLine());
			num2 = Convert.ToInt32(Console.ReadLine());
			result = num1/num2;
		}
		catch(DivideByZeroException  e) //FormatException
		{
			Console.WriteLine("{0}",e.Message);
		}
		catch(FormatException ex)
		{
			Console.WriteLine("{0}",ex.Message);
		}
		finally
		{
			Console.WriteLine("turn over");
		}
	}
	public void display()
	{
		Console.WriteLine("The Result is: {0}",result);
	}
	public static void Main()
	{
		excepation ex = new excepation();
		ex.add();
		ex.display();
	}	
}

Notice that the SqlConnection object is declared outside of the try/catch/finally. The reason is that anything declared in the try/catch cannot be seen by the finally. By declaring it in the previous scope, the finally block is able to access it.

try/finally[edit | edit source]

The try/finally block allows you to do the same as above, but instead errors that are thrown are dealt with by the catch (if possible) and then thrown up the call stack.

class ExceptionTest
{
     public static void Main(string[] args)
     {
          SqlConnection sqlConn = null;

          try
          {
              SqlConnection sqlConn = new SqlConnection ( /*Connection here*/ );
              sqlConn.Open();
 
              // Various DB bits
          }
          finally
          {
               if (sqlConn != null && sqlConn.State != ConnectionState.Closed)
               {
                   sqlConn.Dispose();
               }
          }
     }
}

Re-throwing exceptions[edit | edit source]

Sometimes it is better to throw the error up the call stack for two reasons.

  1. It is not something you would expect to happen.
  2. You are placing extra information into the exception, to help diagnosis.


How not to throw exceptions[edit | edit source]

Some developers write empty try/catch statements like this:

try
{
      // Do something
}
catch (Exception ex)
{
      // Ignore this here
}

This approach is not recommended. You are swallowing the error and continuing on. If this exception was an OutOfMemoryException or a NullReferenceException, it would not be wise to continue. Therefore you should always catch what you would expect to occur, and throw everything else.

Below is another example of how exceptions are caught incorrectly

/* Read the config file, and return the integer value. If it does not exist, then this is a problem! */

try
{
     string value = ConfigurationManager.AppSettings["Timeout"];

     if (value == null)
         throw new ConfigurationErrorsException("Timeout value is not in the configuration file.");
}
catch (Exception ex)
{
     // Do nothing!
}

As you can see, the ConfigurationErrorsException will be caught by the catch (Exception) block, but it is being ignored completely! This is bad programming as you are ignoring the error.

The following is also bad practice:

try
{
   ..
}
catch (Exception ex)
{
     throw ex;
}

The CLR will now think the throw ex; statement is the source of the problem, when the problem is actually in the try section. Therefore never re-throw in this way.

How to catch exceptions[edit | edit source]

A better approach would be:

/* Read the config file, and return the integer value. If it does not exist, then this is a problem! */

try
{
     string value = ConfigurationManager.AppSettings["Timeout"];

     if (value == null)
         throw new ConfigurationErrorsException("Timeout value is not in the configuration file.");
}
catch (Exception ex )
{
     throw; // <-- Throw the existing problem!
}

The throw; keyword means preserve the exception information and throw it up the call stack.

Extra information within exceptions[edit | edit source]

An alternative is to give extra information (maybe local variable information) in addition to the exception. In this case, you wrap the exception within another. You usually use an exception that is as specific to the problem as possible, or create your own, if you cannot find out that is not specific enough (or if there is extra information you would wish to include).

public OrderItem LoadItem(string itemNumber)
{
    DataTable dt = null;

    try
    {
         if (itemNumber == null)
              throw new ArgumentNullException("Item Number cannot be null","itemNumber");

         DataTable dt = DataAccess.OrderItem.Load(itemNumber);
  
         if (dt.Rows == 0)
              return null;
         else if (dt.Rows > 1)
              throw new DuplicateDataException( "Multiple items map to this item.",itemNumber, dt);

         OrderItem item = OrderItem.CreateInstanceFromDataRow(dt.Rows[0]);

         if (item == null)
              throw new ErrorLoadingException("Error loading Item " + itemNumber, itemNumber, dt.Rows[0]);
    }
    catch (DuplicateDataException dde)
    {
         throw new ErrorLoadingException("OrderItem.LoadItem failed with Item " + 
                                                            itemNumber, dde); // <-- Include dde (as the InnerException) parameter
    }
    catch (Exception ex)
    {
         throw; // <-- We aren't expecting any other problems, so throw them if they occur.
    }
}

References[edit | edit source]

  1. [ApplicationException made obsolete]