A try
statement specifies a code block subject to error-handling or cleanup
code. The try
block must be followed by a catch
block, a finally
block, or both.
The catch
block executes
when an error occurs in the try
block.
The finally
block executes
after execution leaves the try
block
(or if present, the catch
block), to
perform cleanup code, whether or not an error occurred.
A catch
block has access to an
Exception
object that contains
information about the error. You use a catch
block to either compensate for the error
or rethrow the exception. You rethrow an exception if
you merely want to log the problem, or if you want to rethrow a new,
higher-level exception type.
A finally
block adds determinism
to your program, by always executing no matter what. It’s useful for
cleanup tasks such as closing network connections.
A try
statement looks like
this:
try { ... // exception may get thrown within execution of // this block } catch (ExceptionA ex) { ... // handle exception of type ExceptionA } catch (ExceptionB ex) { ... // handle exception of type ExceptionB } finally { ... // clean-up code }
Consider the following code:
int x = 3, y = 0; Console.WriteLine (x / y);
Because y
is zero, the runtime
throws a DivideByZeroException
, and our
program terminates. We can prevent this by catching the exception as
follows:
try { int x = 3, y = 0; Console.WriteLine (x / y); } catch (DivideByZeroException ex) { Console.Write ("y cannot be zero. "); } // Execution resumes here after exception...
This is a simple example to illustrate exception handling. We
could deal with this particular scenario better in practice by checking
explicitly for the divisor being zero before calling Calc
.
Exceptions are relatively expensive to handle, taking hundreds of clock cycles.
When an exception is thrown, the CLR performs a test:
Is execution currently within a try
statement that can catch the
exception?
If so, execution is passed to the compatible catch
block. If the catch
block successfully finishes executing,
execution moves to the next statement after the try
statement (if present, executing the
finally
block first).
If not, execution jumps back to the caller of the function, and
the test is repeated (after executing any finally
blocks that wrap the
statement).
If no function in the call stack takes responsibility for the exception, an error dialog is displayed to the user, and the program terminates.
A catch
clause specifies what type of exception to catch. This must
either be System.Exception
or a
subclass of System.Exception
.
Catching System.Exception
catches all
possible errors. This is useful when:
Your program can potentially recover, regardless of the specific exception type.
You plan to rethrow the exception (perhaps after logging it).
Your error handler is the last resort, prior to termination of the program.
More typically, though, you catch specific exception
types, in order to avoid having to deal with circumstances
for which your handler wasn’t designed (e.g., an OutOfMemoryException
).
You can handle multiple exception types with multiple catch
clauses:
try { DoSomething(); } catch (IndexOutOfRangeException ex) { ... } catch (FormatException ex) { ... } catch (OverflowException ex) { ... }
Only one catch
clause executes
for a given exception. If you want to include a safety net to catch more
general exceptions (such as System.Exception
) you must put the more
specific handlers first.
An exception can be caught without specifying a variable, if you don’t need to access its properties:
catch (StackOverflowException) // no variable { ... }
Furthermore, you can omit both the variable and the type (meaning that all exceptions will be caught):
catch { ... }
In languages other than C#, it is possible (though not
recommended) to throw an object that does not derive from Exception
. The CLR automatically wraps that
object in a RuntimeWrappedException
class (which does derive from Exception
).
A finally
block always executes—whether or not an exception is thrown and
whether or not the try
block runs to
completion. finally
blocks are
typically used for cleanup code.
A finally
block executes
either:
After a catch
block
finishes.
After control leaves the try
block because of a jump
statement (e.g., return
or goto
).
After the try
block
ends.
A finally
block helps add
determinism to a program. In the following example, the file that we
open always gets closed, regardless of
whether:
The try
block finishes
normally.
Execution returns early because the file is empty (EndOfStream
).
An IOException
is thrown
while reading the file.
For example:
static void ReadFile()
{
StreamReader reader = null; // In System.IO namespace
try
{
reader = File.OpenText ("file.txt");
if (reader.EndOfStream) return
;
Console.WriteLine (reader.ReadToEnd());
}
finally
{
if (reader != null) reader.Dispose();
}
}
In this example, we closed the file by calling Dispose
on the StreamReader
. Calling Dispose
on an object, within a finally
block, is a standard convention
throughout the .NET Framework and is supported explicitly in C# through
the using
statement.
Many classes encapsulate unmanaged resources, such as file handles,
graphics handles, or database connections. These classes implement
System.IDisposable
, which defines a
single parameterless method named Dispose
to clean up these resources. The
using
statement provides an elegant
syntax for calling Dispose
on an
IDisposable
object within a
finally
block.
The following:
using
(StreamReader reader = File.OpenText ("file.txt"))
{
...
}
StreamReader reader = File.OpenText ("file.txt"); try { ... } finally { if (reader != null) ((IDisposable)reader).Dispose(); }
Exceptions can be thrown either by the runtime or in user code. In this
example, Display
throws a
System.
Argument
Null
Exception
:
static void Display (string name)
{
if (name == null)
throw new ArgumentNullException ("name");
Console.WriteLine (name);
}
You can capture and rethrow an exception as follows:
try { ... }
catch (Exception ex)
{
// Log error
...
throw; // Rethrow same exception
}
Rethrowing in this manner lets you log an error without swallowing it. It also lets you back out of handling an exception should circumstances turn out to be outside what you expected.
If we replaced throw
with
throw ex
, the example would still
work, but the StackTrace
property
of the exception would no longer reflect the original error.
The other common scenario is to rethrow a more specific or meaningful exception type:
try { ... // parse a date of birth from XML element data } catch (FormatException ex) { throw new XmlException ("Invalid date of birth", ex); }
When rethrowing a different exception, you can populate the
InnerException
property with the
original exception to aid debugging. Nearly all types of exceptions
provide a constructor for this purpose (such as in our example).
The following exception types are used widely throughout the CLR and .NET Framework. You can throw these yourself or use them as base classes for deriving custom exception types.
System.ArgumentException
Thrown when a function is called with a bogus argument. This generally indicates a program bug.
System.ArgumentNullException
Subclass of ArgumentException
that’s thrown when a
function argument is (unexpectedly) null
.
System.ArgumentOutOfRangeException
Subclass of ArgumentException
that’s thrown when a
(usually numeric) argument is too big or too small. For example,
this is thrown when passing a negative number into a function that
accepts only positive values.
System.InvalidOperationException
Thrown when the state of an object is unsuitable for a method to successfully execute, regardless of any particular argument values. Examples include reading an unopened file or getting the next element from an enumerator where the underlying list has been modified partway through the iteration.
System.NotSupportedException
Thrown to indicate that a particular functionality is not
supported. A good example is calling the Add
method on a collection for which
IsReadOnly
returns true
.
System.NotImplementedException
Thrown to indicate that a function has not yet been implemented.
System.ObjectDisposedException
Thrown when the object upon which the function is called has been disposed.
In Framework 4.0, Code contracts eliminate
the need for ArgumentException
(and
its subclasses). Code contracts are covered in Chapter 13 of
C# 4.0 in a Nutshell (O’Reilly).