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:
Chapter 6 "Classes": Nested Classes
Chapter 7 "Inheritance": Verifying the Underlying Type with the is Operator, Pattern Matching with the is Operator, Conversion Using the as Operator
Chapter 11 "Exception Handling"
They are also covered in Programming C# 8.0:
Chapter 3 "Types": Nested Types
Chapter 6 "Inheritance": Inheritance and Conversions
Chapter 8 "Exceptions"
Here are a few notes briefly summarizing these topics.
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.
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
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.
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.
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.