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).
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
.
Hence, you need to anticipate all the possible scenarios and handle the exceptions gracefully.
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 |
---|---|
| Gets a collection of key/value pairs that provide additional user-defined information about the exception. |
| Gets or sets a link to the help file associated with this exception. |
| Gets or sets |
| Gets the |
| Gets a message that describes the current exception. |
| Gets or sets the name of the application or the object that causes the error. |
| Gets a string representation of the frames on the call stack at the time the current exception was thrown. |
| 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 theException
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
.
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.
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.
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).
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.
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."
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.
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).
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.
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(); }
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.
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.
3.131.142.80