Chapter 12. Exception Handling

An exception is a situation that occurs when your program encounters an error that it is not expecting during runtime. Examples of exceptions include trying to divide a number by zero, trying to write to a file that is read-only, trying to delete a nonexistent file, and trying to access more members of an array than it actually holds. Exceptions are part and parcel of an application, and as a programmer you need to look out for them by handling the various exceptions that may occur. That means your program must be capable of responding to the exceptions by offering some ways to remedy the problem instead of exiting midway through your program (that is, crashing).

Handling Exceptions

To understand the importance of handling exceptions, consider the following case, a classic example of dividing two numbers:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            int num1, num2, result;

            Console.Write("Please enter the first number:");
            num1 = int.Parse(Console.ReadLine());

            Console.Write("Please enter the second number:");
            num2 = int.Parse(Console.ReadLine());

            result = num1 / num2;
Console.WriteLine("The result of {0}/{1} is {2}", num1, num2, result);

            Console.ReadLine();
        }
    }
}

In this example, there are several opportunities for exceptions to occur:

  • If the user enters a noninteger value for num1 or num2.

  • If the user enters a non-numeric value for num1 and num2.

  • If num2 is zero, resulting in a division by zero error.

Figure 12-1 shows the program halting abruptly when the user enters 3.5 for num1.

Figure 12-1

Figure 12.1. Figure 12-1

Hence, you need to anticipate all the possible scenarios and handle the exceptions gracefully.

Handling Exceptions Using the try-catch Statement

In C#, you can use the try-catch statement to enclose a block of code statements that may potentially cause exceptions to be raised. You enclose these statements within the catch block and that block to catch the different types of exceptions that may occur.

Using the previous example, you can enclose the statements that ask the user to input num1 and num2 and then performs the division within a catch block. You then use the catch block to catch possible exceptions, like this:

static void Main(string[] args)
        {
            int num1, num2, result;
            try
            {
Console.Write("Please enter the first number:");
                num1 = int.Parse(Console.ReadLine());

                Console.Write("Please enter the second number:");
                num2 = int.Parse(Console.ReadLine());

                result = num1 / num2;
                Console.WriteLine("The result of {0}/{1} is {2}",
                    num1, num2, result);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.ReadLine();
        }

The Exception class is the base class for all exceptions; that is, it catches all the various types of exceptions. The class contains the details of the exception that occurred, and includes a number of properties that help identify the code location, the type, the help file, and the reason for the exception. The following table describes these properties.

Property

Description

Data

Gets a collection of key/value pairs that provide additional user-defined information about the exception.

HelpLink

Gets or sets a link to the help file associated with this exception.

HResult

Gets or sets HRESULT, a coded numerical value that is assigned to a specific exception.

InnerException

Gets the Exception instance that caused the current exception.

Message

Gets a message that describes the current exception.

Source

Gets or sets the name of the application or the object that causes the error.

StackTrace

Gets a string representation of the frames on the call stack at the time the current exception was thrown.

TargetSite

Gets the method that throws the current exception.

In the preceding program, if you type in a numeric value for num1 and then an alphabetical character for num2, the exception is caught and displayed like this:

Please enter the first number:6
Please enter the second number:a
Input string was not in a correct format.

If, though, you enter 0 for the second number, you get a different description for the error:

Please enter the first number:7
Please enter the second number:0
Attempted to divide by zero.

Notice that two different types of exceptions are caught using the same Exception class. The description of the exception is contained within the Message property of the Exception class.

You can use the ToString() method of the Exception class to retrieve more details about the exception, such as the description of the exception as well as the stack trace.

However, there are cases where you would like to print your own custom error messages for the different types of exceptions. Using the preceding code, you would not be able to do that — you would need a much finer way to catch the different types of possible exceptions.

To know the different types of exceptions that your program can cause (such as entering "a" for num1 or division by zero), you can set a breakpoint at a line within the catch block and try entering different values. When an exception is raised during runtime, IntelliSense tells you the error and the type of the exception raised. Figure 12-2 shows that the FormatException exception is raised when you enter a for num1.

Figure 12-2

Figure 12.2. Figure 12-2

If you are not sure what type of exception your program is going to raise during runtime, it is always safe to use the base Exception class. If not — if the exception that is raised does not match the exception you are trying to catch — a runtime error will occur. Here's an example:

static void Main(string[] args)
        {
            int num1, num2, result;
            try
            {
                Console.Write("Please enter the first number:");
                num1 = int.Parse(Console.ReadLine());

                Console.Write("Please enter the second number:");
num2 = int.Parse(Console.ReadLine());

                result = num1 / num2;
                Console.WriteLine("The result of {0}/{1} is {2}",
                    num1, num2, result);
            }
            catch (DivideByZeroException ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadLine();
        }

If a division-by-zero exception occurs (entering 0 for num2), the exception is caught. However, if you enter an alphabetic character for num1 or num2, a FormatException exception is raised. And because you are only catching the DivideByZeroException exception, this exception goes unhandled and a runtime error results.

Handling Multiple Exceptions

To handle different types of exceptions, you can have one or more catch blocks in the try-catch statement. The following example shows how you can catch three different exceptions:

  • DivideByZeroException — Thrown when there is an attempt to divide an integral or decimal value by zero.

  • FormatException — Thrown when the format of an argument does not meet the parameter specifications of the invoked method.

  • Exception — Represents errors that occur during application execution.

This example handles the three different exceptions and then prints out a custom error message:

static void Main(string[] args)
        {
            int num1, num2, result;
            try
            {
                Console.Write("Please enter the first number:");
                num1 = int.Parse(Console.ReadLine());

                Console.Write("Please enter the second number:");
                num2 = int.Parse(Console.ReadLine());

                result = num1 / num2;
                Console.WriteLine("The result of {0}/{1} is {2}",
                    num1, num2, result);
            }
            catch (DivideByZeroException ex)
            {
                Console.WriteLine("Division by zero error.");
            }
catch (FormatException ex)
            {
                Console.WriteLine("Input error.");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadLine();
        }

In this program, typing in a numeric value for num1 and an alphabetic character for num2 produces the FormatException exception, which is caught and displayed like this?

Please enter the first number:6
Please enter the second number:a
Input error.

Entering 0 for the second number throws the DivideByZeroException exception, which is caught and displays a different error message:

Please enter the first number:7
Please enter the second number:0
Division by zero error.

So far, all the statements are located in the Main() function. What happens if you have a function called PerformDivision() that divides the two numbers and returns the result, like this?

class Program
    {
        static void Main(string[] args)
        {
            int num1, num2;
            try
            {
                Console.Write("Please enter the first number:");
                num1 = int.Parse(Console.ReadLine());

                Console.Write("Please enter the second number:");
                num2 = int.Parse(Console.ReadLine());

                Program myApp = new Program();

                Console.WriteLine("The result of {0}/{1} is {2}",
                    num1, num2,
                    myApp.PerformDivision(num1, num2));
            }
            catch (DivideByZeroException ex)
            {
                Console.WriteLine("Division by zero error.");
            }
            catch (FormatException ex)
            {
Console.WriteLine("Input error.");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadLine();
        }

        private int PerformDivision(int num1, int num2)
        {
            return num1 / num2;
        }
    }

If num2 is zero, an exception is raised within the PerformDivision() function. You can either catch the exception in the PerformDivision() function or catch the exception in the calling function — Main() in this case. When an exception is raised within the PerformDivision() function, the system searches the function to see if there is any catch block for the exception. If none is found, the exception is passed up the call stack and handled by the calling function. If there is no try-catch block in the calling function, the exception continues to be passed up the call stack again until it is handled. If no more frames exist in the call stack, the default exception handler handles the exception and your program has a runtime error.

Throwing Exceptions Using the throw Statement

Instead of waiting for the system to encounter an error and raise an exception, you can programmatically raise an exception by throwing one. Consider the following example:

private int PerformDivision(int num1, int num2)
        {
            if (num1 == 0) throw new ArithmeticException();
            if (num2 == 0) throw new DivideByZeroException();
            return num1 / num2;
        }

In this program, the PerformDivision() function throws an ArithmeticException exception when num1 is zero and it throws a DivideByZeroException exception when num2 is zero. Because there is no catch block in PerformDivision(), the exception is handled by the calling Main() function. In Main(), you can catch the ArithmeticException exception like this:

class Program
    {
        static void Main(string[] args)
        {
            int num1, num2, result;
            try
            {
                Console.Write("Please enter the first number:");
                num1 = int.Parse(Console.ReadLine());

                Console.Write("Please enter the second number:");
num2 = int.Parse(Console.ReadLine());

                Program myApp = new Program();

                Console.WriteLine("The result of {0}/{1} is {2}",
                    num1, num2,
                    myApp.PerformDivision(num1, num2));
            }
            catch (ArithmeticException ex)
            {
                Console.WriteLine("Numerator cannot be zero.");
            }
            catch (DivideByZeroException ex)
            {
                Console.WriteLine("Division by zero error.");
            }
            catch (FormatException ex)
            {
                Console.WriteLine("Input error");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadLine();
        }

        private int PerformDivision(int num1, int num2)
        {
            if (num1 == 0) throw new  ArithmeticException();
            if (num2 == 0) throw new DivideByZeroException();
            return num1 / num2;
        }
    }

One interesting thing about the placement of the multiple catch blocks is that you place all specific exceptions that you want to catch first before placing generic ones. Because the Exception class is the base of all exception classes, it should always be placed last in a catch block so that any exception that is not caught in the previous catch blocks is always caught. In this example, when the ArithmeticException exception is placed before the DivideByZeroException exception, IntelliSense displays an error (see Figure 12-3).

Figure 12-3

Figure 12.3. Figure 12-3

That's because the DivideByZeroException is derived from the ArithmeticException class, so if there is a division-by-zero exception, the exception is always handled by the ArithmeticException exception and the DivideByZeroException exception is never caught. To solve this problem, you must catch the DivideByZeroException exception first before catching the ArithmeticException exception:

static void Main(string[] args)
        {
            int num1, num2, result;
            try
            {
                Console.Write("Please enter the first number:");
                num1 = int.Parse(Console.ReadLine());

                Console.Write("Please enter the second number:");
                num2 = int.Parse(Console.ReadLine());

                Program myApp = new Program();

                Console.WriteLine("The result of {0}/{1} is {2}",
                    num1, num2,
                    myApp.PerformDivision(num1, num2));
            }
            catch (DivideByZeroException ex)
            {
                Console.WriteLine("Division by zero error.");
            }
            catch (ArithmeticException ex)
            {
                Console.WriteLine("Numerator cannot be zero.");
            }
            catch (FormatException ex)
            {
                Console.WriteLine("Input error.");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadLine();
        }

The following shows the output when different values are entered for num1 and num2:

Please enter the first number:5
Please enter the second number:0
Division by zero error.

Please enter the first number:0
Please enter the second number:5
Numerator cannot be zero.

Please enter the first number:a
Input error.

Rethrowing Exceptions

There are times when after catching an exception, you want to throw the same (or a new type) exception back to the calling function after taking some corrective actions. Take a look at this example:

class Program
    {
        static void Main(string[] args)
        {
            int num1, num2, result;
            try
            {
                Console.Write("Please enter the first number:");
                num1 = int.Parse(Console.ReadLine());

                Console.Write("Please enter the second number:");
                num2 = int.Parse(Console.ReadLine());

                Program myApp = new Program();

                Console.WriteLine("The result of {0}/{1} is {2}",
                    num1, num2,
                    myApp.PerformDivision(num1, num2));
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                if (ex.InnerException != null)
                    Console.WriteLine(ex.InnerException.ToString());
            }
            Console.ReadLine();
        }

        private int PerformDivision(int num1, int num2)
        {
            try
            {
                return num1 / num2;
            }
            catch (DivideByZeroException ex)
            {
                throw new Exception("Division by zero error.", ex);
            }
        }
    }

Here, the PerformDivision() function tries to catch the DivideByZeroException exception and once it succeeds, it rethrows a new generic Exception exception, using the following statements with two arguments:

throw new Exception("Division by zero error.", ex);

The first argument indicates the description for the exception to be thrown, while the second argument is for the inner exception. The inner exception indicates the exception that causes the current exception. When this exception is rethrown, it is handled by the catch block in the Main() function:

catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                if (ex.InnerException != null)
                    Console.WriteLine(ex.InnerException.ToString());
            }

To retrieve the source of the exception, you can check the InnerException property and print out its details using the ToString() method. Here's the output when num2 is zero:

Please enter the first number:5
Please enter the second number:0
Division by zero error.
System.DivideByZeroException: Attempted to divide by zero.
   at ConsoleApp.Program.PerformDivision(Int32 num1, Int32 num2) in C:Documents
 and SettingsWei-Meng LeeMy DocumentsVisual Studio 2008ProjectsConsoleApp ConsoleAppProgram.cs:line 43

As you can see, the message of the exception is "Division by zero error" (set by yourself) and the InnerException property shows the real cause of the error — "Attempted to divide by zero."

Exception Chaining

The InnerException property is of type Exception, and it can be used to store a list of previous exceptions. This is known as exception chaining.

To see how exception chaining works, consider the following program:

class Program
    {
        static void Main(string[] args)
        {
            Program myApp = new Program();

            try
            {
                myApp.Method1();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                if (ex.InnerException != null)
                    Console.WriteLine(ex.InnerException.ToString());
            }
            Console.ReadLine();
        }

        private void Method1()
{
            try
            {
                Method2();
            }
            catch (Exception ex)
            {
                throw new Exception(
                  "Exception caused by calling Method2() in Method1().", ex);
            }
        }

        private void Method2()
        {
            try
            {
                    Method3();
            }
            catch (Exception ex)
            {
                throw new Exception(
                  "Exception caused by calling Method3() in Method2().", ex);
            }
        }

        private void Method3()
        {
            try
            {
                int num1 = 5, num2 = 0;
                int result = num1 / num2;
            }
            catch (DivideByZeroException ex)
            {
                throw new Exception("Division by zero error in Method3().", ex);
            }
        }
    }

In this program, the Main() function calls Method1(), which in turns calls Method2(). Method2() then calls Method3(). In Method3(), a division-by-zero exception occurs and you rethrow a new Exception exception by passing in the current exception (DividebyZeroException). This exception is caught by Method2(), which rethrows a new Exception exception by passing in the current exception. Method1() in turn catches the exception and rethrows a new Exception exception. Finally, the Main() function catches the exception and prints out the result as shown in Figure 12-4.

Figure 12-4

Figure 12.4. Figure 12-4

If you set a breakpoint in the catch block within the Main() function, you will see that the InnerException property contains details of each exception and that all the exceptions are chained via the InnerException property (see Figure 12-5).

Figure 12-5

Figure 12.5. Figure 12-5

Using Exception Objects

Instead of -using the default description for each exception class you are throwing, you can customize the description of the exception by creating an instance of the exception and then setting the Message property. You can also specify the HelpLink property to point to a URL where developers can find more information about the exception. For example, you can create a new instance of the ArithmeticException class using the following code:

if (num1 == 0)
            {
                ArithmeticException ex =
                    new ArithmeticException("Value of num1 cannot be 0.")
                {
                    HelpLink = "http://www.learn2develop.net"
                };
                throw ex;
            }

Here's how you can modify the previous program by customizing the various existing exception classes:

class Program
    {
        static void Main(string[] args)
        {
            int num1, num2;
            try
            {
                Console.Write("Please enter the first number:");
                num1 = int.Parse(Console.ReadLine());

                Console.Write("Please enter the second number:");
                num2 = int.Parse(Console.ReadLine());

                Program myApp = new Program();

                Console.WriteLine("The result of {0}/{1} is {2}",
                    num1, num2,
                    myApp.PerformDivision(num1, num2));
            }
            catch (DivideByZeroException ex)
            {
                Console.WriteLine(ex.Message);
            }
            catch (ArithmeticException ex)
            {
                Console.WriteLine(ex.Message);
            }
            catch (FormatException ex)
            {
                Console.WriteLine(ex.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadLine();
        }

        private int PerformDivision(int num1, int num2)
        {
            if (num1 == 0)
            {
                ArithmeticException ex =
                    new ArithmeticException("Value of num1 cannot be 0.")
                {
                    HelpLink = "http://www.learn2develop.net"
                };
                throw ex;
            }
if (num2 == 0)
            {
                DivideByZeroException ex =
                    new DivideByZeroException("Value of num2 cannot be 0.")
                {
                    HelpLink = "http://www.learn2develop.net"
                };
                throw ex;
            }

            return num1 / num2;
        }
    }

Here's the output when different values are entered for num1 and num2:

Please enter the first number:0
Please enter the second number:5
Value of num1 cannot be 0.

Please enter the first number:5
Please enter the second number:0
Value of num2 cannot be 0.

The finally Statement

By now you know that you can use the try-catch block to enclose potentially dangerous code. This is especially useful for operations such as file manipulation, user input, and so on. Consider the following example:

FileStream fs = null;
            try
            {
                //---opens a file for reading---
                fs = File.Open(@"C:	extfile.txt",
                   FileMode.Open, FileAccess.Read);

                //---tries to write some text into the file---
                byte[] data = ASCIIEncoding.ASCII.GetBytes("some text");
                fs.Write(data, 0, data.Length);

               //---close the file---
                fs.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            //---an error will occur here---
            fs = File.Open(@"C:	extfile.txt", FileMode.Open, FileAccess.Read);

Suppose that you have a text file named textfile.txt located in C:. In this example program, you first try to open the file for reading. After that, you try to write some text into the file, which causes an exception because the file was opened only for reading. After the exception is caught, you proceed to open the file again. However, this fails because the file is still open (the fs.Close() statement within the try block is never executed because the line before it has caused an exception). In this case, you need to ensure that the file is always closed — with or without an exception. For this, you can use the finally statement.

The statement(s) enclosed within a finally block is always executed, regardless of whether an exception occurs. The following program shows how you can use the finally statement to ensure that the file is always closed properly:

FileStream fs = null;
            try
            {
                //---opens a file for reading---
                fs = File.Open(@"C:	extfile.txt",
                   FileMode.Open, FileAccess.Read);

                //---tries to write some text into the file---
                byte[] data = ASCIIEncoding.ASCII.GetBytes("1234567890");
                fs.Write(data, 0, data.Length);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                //---close the file stream object---
                if (fs != null) fs.Close();
            }

            //---this will now be OK---
            fs = File.Open(@"C:	extfile.txt", FileMode.Open, FileAccess.Read);

One important thing about exception handling is that the system uses a lot of resources to raise an exception; thus, you should always try to prevent the system from raising exceptions. Using the preceding example, instead of opening the file and then writing some text into it, it would be a good idea to first check whether the file is writable before proceeding to write into it. If the file is read-only, you simply inform the user that the file is read-only. That prevents an exception from being raised when you try to write into it.

The following shows how to prevent an exception from being raised:

FileStream fs = null;
            try
            {
                //---opens a file for reading---
                fs = File.Open(@"C:	extfile.txt",
                   FileMode.Open, FileAccess.Read);

                //---checks to see if it is writeable---
                if (fs.CanWrite)
{
                    //---tries to write some text into the file---
                    byte[] data = ASCIIEncoding.ASCII.GetBytes("1234567890");
                    fs.Write(data, 0, data.Length);
                } else
                    Console.WriteLine("File is read-only");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                //---close the file stream object---
                if (fs != null) fs.Close();
            }

Creating Custom Exceptions

The .NET class libraries provide a list of exceptions that should be sufficient for most of your uses, but there may be times when you need to create your own custom exception class. You can do so by deriving from the Exception class. The following is an example of a custom class named AllNumbersZeroException:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public class AllNumbersZeroException : Exception
{
    public AllNumbersZeroException()
    {
    }
    public AllNumbersZeroException(string message)
        : base(message)
    {
    }
    public AllNumbersZeroException(string message, Exception inner)
        : base(message, inner)
    {
    }
}

To create your own custom exception class, you need to inherit from the Exception base class and implement the three overloaded constructors for it.

The AllNumbersZeroException class contains three overloaded constructors that initialize the constructor in the base class. To see how you can use this custom exception class, let's take another look at the program you have been using all along:

static void Main(string[] args)
        {
            int num1, num2, result;
            try
            {
                Console.Write("Please enter the first number:");
                num1 = int.Parse(Console.ReadLine());

                Console.Write("Please enter the second number:");
                num2 = int.Parse(Console.ReadLine());

                Program myApp = new Program();

                Console.WriteLine("The result of {0}/{1} is {2}",
                    num1, num2,
                    myApp.PerformDivision(num1, num2));
            }

            catch (AllNumbersZeroException ex)
            {
                Console.WriteLine(ex.Message);
            }
            catch (DivideByZeroException ex)
            {
                Console.WriteLine(ex.Message);
            }
            catch (ArithmeticException ex)
            {
                Console.WriteLine(ex.Message);
            }
            catch (FormatException ex)
            {
                Console.WriteLine(ex.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadLine();
        }

        private int PerformDivision(int num1, int num2)
        {
            if (num1 == 0 && num2 == 0)
            {
                AllNumbersZeroException ex =
                    new AllNumbersZeroException("Both numbers cannot be 0.")
{
                        HelpLink = "http://www.learn2develop.net"
                    };
                throw ex;
            }
            if (num1 == 0)
            {
                ArithmeticException ex =
                    new ArithmeticException("Value of num1 cannot be 0.")
                {
                    HelpLink = "http://www.learn2develop.net"
                };
                throw ex;
            }
            if (num2 == 0)
            {
                DivideByZeroException ex =
                    new DivideByZeroException("Value of num2 cannot be 0.")
                {
                    HelpLink = "http://www.learn2develop.net"
                };
                throw ex;
            }
            return num1 / num2;
        }

This program shows that if both num1 and num2 are zero, the AllNumbersException exception is raised with the custom message set.

Here's the output when 0 is entered for both num1 and num2:

Please enter the first number:0
Please enter the second number:0
Both numbers cannot be 0.

Summary

Handling exceptions is part and parcel of the process of building a robust application, and you should spend considerable effort in identifying code that is likely to cause an exception. Besides catching all the exceptions defined in the .NET Framework, you can also define your own custom exception containing your own specific error message.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.131.142.80