try Statements and Exceptions

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
{
  ... // cleanup 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...

Note

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.

The catch Clause

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 { ... }

The finally Block

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.

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"))
{
  ...
}

is precisely equivalent to:

StreamReader reader = File.OpenText ("file.txt");
try
{
  ...
}
finally
{
  if (reader != null) ((IDisposable)reader).Dispose();
}

Throwing Exceptions

Exceptions can be thrown either by the runtime or in user code. Here, Display throws a System.ArgumentNullException:

static void Display (string name)
{
  if (name == null)
    throw new ArgumentNullException ("name");

  Console.WriteLine (name);
}

Rethrowing an exception

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.

Note

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).

Key Properties of System.Exception

The most important properties of System.Exception are the following:

StackTrace

A string representing all the methods that are called from the origin of the exception to the catch block.

Message

A string with a description of the error.

InnerException

The inner exception (if any) that caused the outer exception. This, itself, may have another InnerException.

Common Exception Types

The following exception types are used widely throughout the CLR and the .NET Framework. You can throw them 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.

Note

The need for ArgumentException (and its subclasses) is eliminated by code contracts, which are covered in Chapter 13 of C# 5.0 in a Nutshell.

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

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