O'Reilly logo

C# in a Nutshell by Peter Drayton, Ted Neward, Ben Albahari

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Try Statements and Exceptions

try statement-block
[catch (exception type value?)? 
 statement-block]+ |
finally statement-block |
[catch (exception type value?)? 
 statement-block]+
finally statement-block

The purpose of a try statement is to simplify program execution in exceptional circumstances — typically, an error. A try statement does two things. First, it lets the catch block catch exceptions thrown during the try block’s execution. Second, it ensures that execution cannot leave the try block without first executing the finally block. A try block must be followed by a catch block(s), a finally block, or both. The form of a try block looks like this:

try {
  ... // exception may be thrown during execution of this function
}
catch (ExceptionA ex) {
  ... // react to exception of type ExceptionA
}
catch (ExceptionB ex) {
  ... // react to exception of type ExceptionB
}
finally {
  ... // code to always run after try block executes, even if
  ... // an exception is not thrown
}

Exceptions

C# exceptions are objects that contain information representing the occurrence of an exceptional program state. When an exceptional state has occurred (e.g., a method receives an illegal value), an exception object may be thrown, and the call-stack is unwound until the exception is caught by an exception-handling block. For example:

using System;
  
namespace TryStatementsAndExceptions {
  public class WeightCalculator {
    public static float CalcBMI (float weightKilos, float metersTall) {
      if (metersTall < 0 || metersTall > 3)
        throw new ArgumentException ("Impossible Height", "metersTall");
      if (metersTall < 0 || weightKilos > 1000)
        throw new ArgumentException ("Impossible Weight", "weightKilos");
      return weightKilos / (metersTall*metersTall);
    }
  }
  class Test {
    static void Main () {
      TestIt ();
    }
    static void TestIt () {
      try {
        float bmi = WeightCalculator.CalcBMI (100, 5);
        Console.WriteLine(bmi);
      }
      catch(ArgumentException ex) {
        Console.WriteLine(ex);
      }
      finally {
        Console.WriteLine ("Thanks for running the program");
      }
      Console.Read();
    }
  }
}

In this example, calling CalcBMI throws an ArgumentException indicating that it’s impossible for someone to be five meters tall. Execution leaves CalcBMI and returns to the calling method, TestIt, which handles the ArgumentException, and displays the exception to the Console. Next, the finally method is executed, which prints “Thanks for running the program” to the Console. Without our try statement, the call stack would have been unwound right back to the Main method, and the program would terminate.

The catch Clause

A catch clause specifies the exception type (including derived types) to catch. An exception must be of type System.Exception, or type that derives from System.Exception. Catching System.Exception provides the widest possible net for catching errors, which is useful if your handling of the error is totally generic, such as an error-logging mechanism. Otherwise, you should catch a more specific exception type, to prevent your catch block from having to deal with a circumstance it wasn’t designed to handle (e.g., an out-of-memory exception).

Omitting the exception variable

Specifying only an exception type without a variable name allows an exception to be caught when we don’t need to use the exception instance and merely knowing its type is enough. The previous example could be written like this:

catch(ArgumentException) { // don't specify variable
  Console.WriteLine("Couldn't calculate ideal weight!");
}

Omitting the catch expression

You may also entirely omit the catch expression altogether. This catches an exception of any type, even types thrown by other non-CLS-compliant languages that are not derived from System.Exception. The previous example could be written like this:

catch {
  Console.WriteLine("Couldn't calculate ideal weight!");
}

Specifying multiple catch clauses

When declaring multiple catch clauses, only the first catch clause with an exception type that matches the thrown exception executes its catch block. It is illegal for an exception type B to precede an exception type D if B is a base class of D, since it would be unreachable.

try {...}
catch (NullReferenceException) {...}
catch (ArgumentException) {...}
catch {...}

The finally Block

A finally block is always executed when control leaves the try block. A finally block is executed at any of the following periods:

  • Immediately after the try block completes

  • Immediately after the try block prematurely exits with a jump statement (e.g., return, goto), and immediately before the target of the jump statement

  • Immediately after a catch block executes

finally blocks can add determinism to a program’s execution by ensuring that the specified code always gets executed.

In our main example, if the height passed to the calculator is invalid, an ArgumentException is thrown that executes the catch block, followed by the finally block. However, if anything else goes wrong, the finally block is still executed. This ensures that we say goodbye to our user before exiting the program.

Key Properties of System.Exception

Notable properties of System.Exception include:

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

This cascading exception structure can be particularly useful when debugging. Sometimes it is useful to catch an exception, then throw a new, more specific exception. For instance, we may catch an IOException, and then throw a ProblemFooingException that contains more specific information on what went wrong. In this scenario, the ProblemFooingException should include the IOException as the InnerException argument in its constructor, which is assigned to the InnerException property.

Tip

Note that in C# all exceptions are runtime exceptions — there is no equivalent to Java’s compile-time checked exceptions.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required