Week 6: Notes

There was no lecture or tutorial this week.

Our topics this week are the is/as operators, exception handling, and nested classes. You can read about them in Essential C# 7.0:

They are also covered in Programming C# 8.0:

Here are a few notes briefly summarizing these topics.

is

The is operator returns true if a value is not null and belongs to a given type. It works with both reference types and nullable types:

    class LinkedStack : Stack {  }
     

    Stack s = getStack();
    if (s is LinkedStack)
      WriteLine("it is a LinkedStack");
    int? i = abc();
    if (i is int)  // true if i != null
      WriteLine(i.Value);
    

The is operator can optionally bind a variable. The examples above can be rewritten as follows:

    Stack s = getStack();
    if (s is LinkedStack ls)
        WriteLine("it is a LinkedStack");

    if (abc() is int i)
        WriteLine(i);

This variable binding works not only in if statements, but also in while loops. This is often convenient when we want to loop as long as a function returns a non-null value. For example, instead of

    while (true) {
        s = ReadLine();
        if (s == null)
            break;
        WriteLine(s);
    }

we can use is:

    while (ReadLine() is string s)
        WriteLine(s);

In this loop, when ReadLine() returns a non-null value, the string variable s receives that value and the loop continues. When ReadLine() returns null, the loop terminates.

as

The as operator checks whether a value belongs to a type. If so, it returns the value; otherwise it returns null:

    Stack s = getStack();
    LinkedStack ls = s as LinkedStack;  // if s was not a LinkedStack, ls will be null

throw

The throw statement throws an exception, which can be any object belonging to the System.Exception class or any of its subclasses. The exception will pass up the call stack, aborting the execution of any methods in progress until it is caught with a try...catch block at some point. If the exception is not caught, the program will terminate.

try/catch/finally

The try statement attempts to execute a block of code. It may have a set of catch clauses and/or a finally clause.

A catch clause catches all exceptions of a certain type. For example:

  static void Main() {
    StreamReader reader;
    try {
      reader = new StreamReader("numbers");
    } catch (FileNotFoundException e) {
      WriteLine("can't find input file: " + e.FileName);
      return;
    }
    

The code above will catch an exception of class FileNotFoundException, or of any subclass of it. When an exception is caught, the catch block (called an exception handler) executes. The catch block may itself rethrow the given exception, or even a different exception. If the catch block does not throw an exception, execution resumes below the try statement.

A finally clause will always execute, even if an exception is thrown inside the body of the try statement. For example:

  StreamReader reader = ;
  StreamWriter writer = ;
  try {
    while (reader.ReadLine() is string s)
      writer.WriteLine(transform(s));
  } finally {
    reader.Close();
    writer.Close();
  }

In this close, reader and writer will be closed even if an exception occurs within the try body (for example, within the transform method). Note that a finally clause does not itself catch an exception, which will continue to pass up the call stack.

nested classes

In C# a class may be nested inside another class. You may reasonably want to use a nested class when you write a helper class that is useful only inside another class. For example:

class LinkedList {
  class Node {
    public int i;
    public Node next;
    public Node(int i, Node next) { this.i = i; this.next = next; }
  }
  
  Node head;
  
  public void prepend(int i) {
    head = new Node(i, head);
  }
}

The Node class is nested inside LinkedList. It is visible inside that class, but since the Node class is not explicitly marked as public, it is not accessible from outside LinkedList.

A class that is nested inside a generic class may use all the type variables of the containing class. This can be quite convenient. For example, let's make the previous example generic:

class LinkedList<T> {
  class Node {
    public T val;
    public Node next;
    public Node(T val, Node next) { this.val = val; this.next = next; }
  }
  
  Node head;
  
  public void prepend(T val) {
    head = new Node(val, head);
  }
}

Notice that we do not need to declare Node as a generic class Node<T>. If Node were not nested, we would have to declare it as generic since it would be outside the scope of the type variable T. And then inside the LinkedList class we would have to write Node<T> whenever we referred to that class.