only for RuBoard - do not distribute or recompile Previous Section Next Section

2.15 try Statements and Exceptions

Syntax:

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

2.15.1 try Statement

The purpose of a try statement is to simplify dealing with program execution in exceptional circumstances. A try statement does two things. First, it lets exceptions thrown during the try block's execution be caught by the catch block. Second, it ensures that execution can't leave the try block without first executing the finally block. A try block must be followed by one or more catch blocks, a finally block, or both.

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

In the following example, we have an Account class. An exceptional state for the account class is when its balance is below zero, which occurs when the Withdraw method receives a withdraw amount larger than the current balance. Our test class makes our customer tiffanyTaylor perform several actions that involve despositing and withdrawing from an account. When she attempts to withdraw more money than she has in her account, a FundException is thrown, which we will catch so we can notify the user and display her current account balance.

using System;

public class FundException : Exception {
  public FundException(decimal balance) :
    base("Total funds are "+balance+" dollars") {}
}

class Account {
  decimal balance;
  public decimal Balance {
    get {return balance;}
  }
  public void Deposit(decimal amount) {
    balance += amount;
  }
  public void Withdraw(decimal amount) {
    if (amount > balance)
      throw new FundException(balance);
    balance -= amount;
  }
}

class Customer {
  Account savingsAccount = new Account(  );
  public void SellBike(  ) {
    savingsAccount.Deposit (1000);
  }
  public void BuyCar(  ) {
    savingsAccount.Withdraw (30000);
  }
  public void GoToMovie(  ) {
    savingsAccount.Withdraw (20);
  }
}

class Test {
  static void Main(  ) {
    Customer tiffanyTaylor = new Customer(  );
    bool todaysTasksDone = false;
    try {
      tiffanyTaylor.SellBike(  );
      tiffanyTaylor.BuyCar(  );
      tiffanyTaylor.GoToMovie(  );
      todaysTasksDone = true;
    }
    catch (FundException ex) {
      Console.WriteLine(ex.Message);
    }
    finally {
      Console.WriteLine(todaysTasksDone);
    }
  }
}

At the point when the FundException is thrown, the call stack is comprised of three methods: Withdraw, BuyCar, and Main. Each of these methods will in succession return immediately to its calling method until one of them has a catch block that can handle the exception, in this case Main. Without a try statement, the call stack would have been unwound completely, and our program would have terminated without displaying what caused the problem and without letting the user know whether today's tasks were completed.

2.15.3 catch

A catch clause specifies which exception type (including derived types) to catch. An exception must be of type System.Exception or of a 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 with an error-logging mechanism. Otherwise, you should catch a more specific exception type to prevent your catch block from dealing with a circumstance it wasn't designed to handle (for example, an out-of-memory exception).

2.15.3.1 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 will suffice. The previous example could have been written like this:

catch(FundException) { // don't specify variable
  Console.WriteLine("Problem with funds");
}
2.15.3.2 Omitting the catch expression

You may also completely omit the catch expression. This will catch an exception of any type, even types that are not derived from System.Exception (these could be thrown by non-CLS-compliant languages). The previous example could have been written like this:

catch {
  Console.WriteLine("Something went wrong...");
}

The fact that most exceptions do inherit from System.Exception is a CLS convention, not a CLR requirement.

2.15.3.3 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 (IOException) {...}
catch {...}

2.15.4 finally

finally blocks are always executed when control leaves the try block. A finally block is executed at one of the following times:

finally blocks add determinism to a program's execution by ensuring that particular code always gets executed.

In our main example, if some other exception occurred such as a memory exception, the finally block would have still been executed. This ensures that the user would know whether today's tasks were completed. The finally block can also be used to gracefully restore program state when an exception occurs. Had our example used a connection to a remote account object, it would have been appropriate to close the account in the finally block.

2.15.5 Key Properties of the System.Exception Class

You will most frequently use the following properties of System.Exception:

StackTrace

This is a string representing all the methods called from the origin of the exception to the catch block.

Message

This is a string with a description of the error.

InnerException

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

In C# all exceptions are runtime exceptions; there is no equivalent of Java's compile-time exceptions.

only for RuBoard - do not distribute or recompile Previous Section Next Section