Week 2: Notes

This week's topics are covered in Essential C#: see ch. 3 More with Data Types (Tuples, Arrays), ch. 5 Methods and Parameters, ch. 6 Classes.

Here is a summary of the C# elements we discussed:

conditional operator

The conditional operator (? :) is similar to the if statement, but it is an expression, not a statement. It takes a Boolean value and two extra values. It returns the first of these values if the boolean is true, or the second if it is false. For example, the following statement computes the greater of i and j and assigns it to k:

    int i, j;
    ...
    int k = i > j ? i : j;

switch

    switch (i) {
      case 1:
        WriteLine("one");
        break;
        
      case 2:

      case 3:
        WriteLine("two or three");
        return;
      
      default:
        WriteLine("other");
        break;
    }

The switch statement is a more compact (and possibly more efficient) alternative to a series of if statements. The default section is optional. Each case in a switch statement must end with a break or return statement.

foreach

    foreach (char c in "hello")
      Write(c + " ");

foreach iterates over each element of a collection. The only kinds of collections we have learned about so far are strings and arrays (but we will soon see others).

A foreach statement always declares an iteration variable whose scope is the body of the statement.

tuples

A tuple holds two or more values. Unlike arrays, tuples have fixed length and can hold values of varying types.

A tuple type is written as a list of element types in parentheses. For example:

    (int, string) p = (3, "hello");

We can access a tuple's elements using the property names Item1, Item2 and so on:

    WriteLine(p.Item1);    // writes 3

    WriteLine(p.Item2);    // writes "hello"

We can use multiple assignment to unpack a tuple into a set of variables:

    (int x, string s) = p;   // now x is 3, s = "hello"

named tuples

The generic element names Item1 and Item2 are not very informative. Sometimes it is clearer to use a named tuple, which specifies names for the elements. For example:

    (int x, int y) p = (10, 20);

Now we can access the tuple elements by name:

    WriteLine($"x = {p.x}, y = {p.y}");

Be sure to note the difference between the syntax for multiple assignment and the syntax for creating a named tuple – these may look similar at first. The declaration above creates a single variable p with fields p.x and p.y. By contrast, the line

    (int x, int y) = (10, 20);

is a multiple assigment that creates local variables x and y.

multidimensional arrays

There are two kinds of multidimensional arrays: rectangular arrays and jagged arrays.

You can dynamically allocate a rectangular array using the new operator:

    int[,] a = new int[3, 4];

Or you can allocate an array and initialize it with a set of values:

    int[,] a = { {1, 4, 5}, {2, 3, 6} };

You can access an element of a rectangular array using an expression such as a[2, 5].

An rectangular array may even have 3 or more dimensions, e.g.

    int[,,] a = new int[5, 10, 15];    // a 3-dimensional array

The Length property returns the total number of elements in a rectangular array. This is the product of the lengths of all dimensions. The Rank property returns the number of dimensions, and the method int GetLength(int d) returns the length of dimension d.

A jagged array is an array of arrays. Unlike a rectangular multidimensional arrays, each subarray in a jagged array can have a different length.

You can allocate a jagged array like this:

    int[][] i = new int[3][];
    i[0] = new int[2];
    i[1] = new int[4];
    i[2] = new int[5];

If you like, you can initialize a jagged array as you allocate it:

    int[][] i = {
      new int[] { 2, 3},
      new int[] { 3, 4, 5},
      new int[] { 1, 3, 5, 7, 9}
    };

You can access an element of a jagged array using an expression such as a[2][5].

classes

A class is a user-defined data type that can contain fields, constructors, methods, and other kinds of members.

Here is a definition for a simple class implementing a fixed-size stack:

class Stack {

    // fields

    int[] a;
    int count;
    
    // constructor

    public Stack(int maxSize) {    
        a = new int[maxSize];
        count = 0;
    }
    
    // methods

    public bool isEmpty() {
        return count == 0;
    }
    
    public void push(int i) {
        a[count] = i;
        count += 1;
    }
    
    public int pop() {
        count -= 1;
        return a[count];
    }
}

access levels

Every member in a class can be either public or private. public members are accessible everywhere, and private members are accessible only within the class.

The default access level is private.

fields

Each instance of a class contains a set of fields.

A field may be declared with an initial value:

class Foo {
  int x = 3, y = 4;
  

}

If you don't specify an initial value, a field will intially be set to its type's default value (e.g 0 for an int).

Code in a constructor or method may refer to fields by name and may modify their values.

constructors

A constructor makes a new instance of a class. It always has the same name as its containing class.

A constructor will often intialize fields. For example:

class Point {
  double x, y;
  
  public Point(double x, double y) {
    this.x = x;
    this.y = y;
  }

}

To call a constructor, use the new operator and pass any required arguments:

    Point p = new Point(3.0, 4.0);

If a class definition includes no constructors, then C# provides a default constructor that is public and takes no arguments.

Constructors may be overloaded, meaning that a class may contain several constructors with different numbers and/or types of arguments.

A constructor may call another constructor; this is called constructor chaining. For example, we could add a second constructor to the Point class that takes no arguments, and constructs a Point at the origin (0,0):

    public Point() : this(0.0, 0.0) {  }

this

this is a special value that refers to the object on which a constructor or method was invoked.

methods

A method is like a function, but belongs to a class. In a method declaration, the return type precedes the method name:

    int mul(int x, int y) {
      return x * y;
    }

overloaded methods

You may declare multiple methods that have the same name but have different numbers and/or types of parameters. This is called method overloading. For example:

  int abc(int i) {
    return i + 1;
  }

  int abc(int i, int j) { 
    return i + j;
  }
  
  string abc(string s) {
    return s + s;
  }

expression-bodied methods

If a method’s body is a simple expression, the method can be defined using a compact syntax:

    int mul(int x, int y) => x * y;

static members

A field or method may be static. Static members are shared by all instances of a class.

    class Foo {
      static int x = 1;
      static int y;

      static void inc() { x += 1; }
    }

A static method is invoked on a class, not on an instance:

    Foo.inc();

Static methods may access private members of any instance of their class. For example, we can add a static method to the Point class that compares two Point objects and returns true if they are equal:

  public static bool eq(Point p, Point q) =>
    p.x == q.x && p.y == q.y;

value and reference types

Every type in C# is either a value type or a reference type.

When a variable's type is a value type, the variable holds a value. An assignment between variables of value type copies the value from one variable to another.

When a variable's type is a reference type, the variable holds a reference to an object. An assignment between variables of reference type makes them refer to the same object.

For example:

    int[] a = { 4, 5, 6 };
    int[] b = a;       // now b and a refer to the same array
    a[1] = 7;
    WriteLine(b[1]);   // writes 7

using static

At the top of a source file, you can use a using static declaration to import all static methods from a given class, so that you can use these methods without prefixing with them with the class name. For example, if you write

    using static System.Math;

then your code will be able to call Sqrt() and Exp() directly without having to write Math.Sqrt() and Math.Exp(). This is often convenient.