Examples Using Specific catch Clauses
Finding a Handler for an Exception
Throwing Without an Exception Object
An exception is a run-time error in a program that violates a system or application constraint, or a condition that is not expected to occur during normal operation. Examples are when a program tries to divide a number by zero or tries to write to a read-only file. When these occur, the system catches the error and raises an exception.
If the program has not provided code to handle the exception, the system will halt the program. For example, the following code raises an exception when it attempts to divide by zero:
static void Main()
{
int x = 10, y = 0;
x /= y; // Attempt to divide by zero--raises an exception
}
When this code is run, the system displays the following error message:
Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
at Exceptions_1.Program.Main() in C:ProgsExceptionsProgram.cs:line 12
The try
statement allows you to designate blocks of code to be guarded for exceptions and to supply code to handle them if they occur. The try
statement consists of three sections, as shown in Figure 11-1.
try
block contains the code that is being guarded for exceptions.catch
clauses section contains one or more catch
clauses. These are blocks of code to handle the exceptions. They are also known as exception handlers.finally
block contains code to be executed under all circumstances, whether or not an exception is raised.Figure 11-1. Structure of the try statement
The previous example showed that attempting to divide by zero causes an exception. You can modify the program to handle that exception by placing the code inside a try
block and supplying a simple catch
clause. When the exception is raised, it is caught and handled in the catch
block.
static void Main()
{
int x = 10;
try
{
int y = 0;
x /= y; // Raises an exception
}
catch
{
... // Code to handle the exception
Console.WriteLine("Handling all exceptions - Keep on Running");
}
}
This code produces the following message. Notice that, other than the output message, there is no indication that an exception has occurred.
Handling all exceptions - Keep on Running
There are many different types of exceptions that can occur in a program. The BCL defines a number of exception classes, each representing a specific type. When one occurs, the CLR does the following:
catch
clause to handle it.All exception classes are ultimately derived from the System.Exception
class. Figure 11-2 shows a portion of the exception inheritance hierarchy.
Figure 11-2. Structure of the exception hierarchy
An exception object contains read-only properties with information about the exception that caused it. Table 11-1 shows some of these properties.
Table 11-1. Selected Properties of an Exception Object
Property | Type | Description |
Message |
string |
This property contains an error message explaining the cause of the exception. |
StackTrace |
string |
This property contains information describing where the exception occurred. |
InnerException |
Exception |
If the current exception was raised by another exception, this property contains a reference to the previous exception. |
HelpLink |
string |
This property can be set by application-defined exceptions to give a URN or URL for information on the cause of the exception. |
Source |
string |
If not set by an application-defined exception, this property contains the name of the assembly where the exception originated. |
The catch
clause handles exceptions. There are three forms, allowing different levels of processing. Figure 11-3 shows the forms.
Figure 11-3. The three forms of the catch clause
The general catch
clause can accept any exception but can't determine the type of exception that caused it. This allows only general processing and cleanup for whatever exception might occur.
The specific catch
clause form takes the name of an exception class as a parameter. It matches exceptions of the specified class or exception classes derived from it.
The specific catch
clause with object form gives you the most information about the exception. It matches exceptions of the specified class, or exception classes derived from it. It gives you a reference to the exception object created by the CLR, by assigning it to the exception variable. You can access the exception variable's properties within the block of the catch
clause to get specific information about the exception raised.
For example, the following code handles exceptions of type IndexOutOfRangeException
. When one occurs, a reference to the actual exception object is passed into the code with parameter name e
. The three WriteLine
statements each read a string field from the exception object.
Going back to our divide-by-zero example, the following code modifies the previous catch
clause to specifically handle exceptions of the DivideByZeroException
class. While in the previous example, the catch
clause would handle any exception raised in the try
block, the current example will only handle those of the DivideByZeroException
class.
You could further modify the catch
clause to use an exception variable. This allows you to access the exception object inside the catch
block.
This code produces the following output:
Message: Attempted to divide by zero.
Source: Exceptions 1
Stack: at Exceptions_1.Program.Main() in C:ProgsExceptions 1
Exceptions 1Program.cs:line 14
The purpose of a catch
clause is to allow you to handle an exception in an elegant way. If your catch
clause is of the form that takes a parameter, then the system has set that exception variable to a reference to the exception object, which you can inspect to determine the cause of the exception. If the exception was the result of a previous exception, you can get a reference to that previous exception's exception object from the exception variable's InnerException
property.
The catch
clauses section can contain multiple catch
clauses. Figure 11-4 shows a summary of the catch
clauses section.
Figure 11-4. Structure of the catch clauses section of a try statement
When an exception is raised, the system searches the list of catch
clauses in order, and the first catch
clause that matches the type of the exception object is executed. Because of this, there are two important rules in ordering the catch
clauses. They are the following:
catch
clauses must be ordered with the most specific exception types first, progressing to the most general. For example, if you declare an exception class derived from NullReferenceException
, the catch
clause for your derived exception type should be listed before the catch
clause for NullReferenceException
.catch
clause, it must be last, after all specific catch
clauses. Using the general catch
clause is discouraged. You should use one of the specific catch
clauses if at all possible. The general catch
clause hides bugs by allowing the program to continue execution and can leave the program in an unknown state.If a program's flow of control enters a try
statement that has a finally
block, the finally
block is always executed. Figure 11-5 shows the flow of control.
try
block, then at the end of the try
block, control skips over any catch
clauses and goes to the finally
block.try
block, then the appropriate catch
clause in the catch
clauses section is executed, followed by execution of the finally
block.Figure 11-5. Execution of the finally block
The finally
block will always be executed before returning to the calling code, even if a try
block has a return
statement or an exception is thrown in the catch block. For example, in the following code, there is a return
statement in the middle of the try
block that is executed under certain conditions. This does not allow it to bypass the finally
statement.
try
{
if (inVal < 10) {
Console.Write("First Branch - ");
return;
}
else
Console.Write("Second Branch - ");
}
finally
{ Console.WriteLine("In finally statement"); }
This code produces the following output when variable inVal
has the value 5
:
First Branch - In finally statement
When a program raises an exception, the system checks to see whether the program has provided a handler for it. Figure 11-6 shows the flow of control.
try
block, the system will check to see whether any of the catch
clauses can handle the exception.catch
clause is found, the one of the following happens:
catch
clause is executed.finally
block, it is executed.try
statement (that is, after the finally
block, or after the last catch
clause if there is no finally
block).Figure 11-6. Exception with handler in current try statement
If the exception was raised in a section of code that was not guarded by a try
statement or if the try
statement does not have a matching exception handler, the system will have to look further for a matching handler. It will do this by searching down the call stack, in sequence, to see whether there is an enclosing try
block with a matching handler.
Figure 11-7 illustrates the search process. On the left of the figure is the calling structure of the code, and on the right is the call stack. The figure shows that Method2
is called from inside the try
block of Method1
. If an exception occurs inside the try
block in Method2
, the system does the following:
Method2
has exception handlers that can handle the exception.
Method2
handles it, and program execution continues.Method1
, searching for an appropriate handler.Method1
has an appropriate catch
clause, the system does the following:
Method2
Method2
's finally
block and pops Method2
off the stackMethod1
's catch
clause and its finally
blockMethod1
doesn't have an appropriate catch
clause, the system continues searching down the call stack.Figure 11-7. Searching down the call stack
Figure 11-8 shows the general algorithm for handling an exception.
Figure 11-8. The general algorithm for handling an exception
In the following code, Main
starts execution and calls method A
, which calls method B
. A description and diagram of the process are given after the code and in Figure 11-9.
class Program
{
static void Main()
{
MyClass MCls = new MyClass();
try
{ MCls.A(); }
catch (DivideByZeroException e)
{ Console.WriteLine("catch clause in Main()"); }
finally
{ Console.WriteLine("finally clause in Main()"); }
Console.WriteLine("After try statement in Main.");
Console.WriteLine(" -- Keep running.");
}
}
class MyClass
{
public void A()
{
try
{ B(); }
catch (System.NullReferenceException)
{ Console.WriteLine("catch clause in A()"); }
finally
{ Console.WriteLine("finally clause in A()"); }
}
void B()
{
int x = 10, y = 0;
try
{ x /= y; }
catch (System.IndexOutOfRangeException)
{ Console.WriteLine("catch clause in B()"); }
finally
{ Console.WriteLine("finally clause in B()"); }
}
}
This code produces the following output:
finally clause in B()
finally clause in A()
catch clause in Main()
finally clause in Main()
After try statement in Main.
-- Keep running.
Main
calls A
, which calls B
, which encounters a DivideByZeroException
exception.B
's catch
section for a matching catch
clause. Although it has one for IndexOutOfRangeException
, it doesn't have one for DivideByZeroException
.A
's catch
section, where it finds that A
also doesn't have a matching catch
clause.Main
's catch
clause section, where it finds that Main
does have a DivideByZeroException
catch
clause.catch
clause has now been located, it is not executed yet. Instead, the system goes back to the top of the stack, executes B
's finally
clause, and pops B
from the call stack.A
, executes its finally
clause, and pops A
from the call stack.Main
's matching catch
clause is executed, followed by its finally
clause. Execution then continues after the end of Main
's try
statement.Figure 11-9. Searching the stack for an exception handler
You can make your code explicitly raise an exception by using the throw
statement. The syntax for the throw
statement is the following:
throw ExceptionObject;
For example, the following code defines a method called PrintArg
, which takes a string
argument and prints it out. Inside the try
block, it first checks to make sure the argument is not null
. If it is, it creates an ArgumentNullException
instance and throws it. The exception instance is caught in the catch
statement, and the error message is printed. Main
calls the method twice: once with a null
argument and then with a valid argument.
This code produces the following output:
Message: Value cannot be null.
Parameter name: arg
Hi there!
The throw
statement can also be used without an exception object, inside a catch
block.
catch
statement.For example, the following code rethrows the exception from inside the first catch
clause:
This code produces the following output:
Inner Catch: Value cannot be null.
Parameter name: arg
Outer Catch: Handling an Exception.
18.227.10.45