Week 4: Notes

enums

An enum type holds one of a fixed set of constant values:

enum Suit {
    Club, Diamond, Heart, Spade
}

To refer to one of these values, prefix it with the type name:

Suit s = Suit.Diamond;

If you'd like to be able to access an enum's values without the type name prefix, include a using static declaration at the top of your source file:

using static Suit;

Then you will be able to write, for example:

Suit s = Diamond;

Internally, an enumerated value is stored as an integer. Each constant in an enum is assigned an integer value, starting from 0. For example, in the enumeration above, Diamond is assigned the value 1.

Explicit conversions exist between each enum type and int in both directions:

Suit s = Suit.Diamond;
int i = (int) s;    // convert a Suit to an int
Suit t = (Suit) i;  // convert an int to a Suit

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

constants

We have seen that classes may contain fields and methods. Another kind of class member is constants. For example:

class Stack {
    const int LIMIT = 100;

    int[] a = new a[LIMIT];
    ...
}

Like other kinds of members, a constant may be either public or private.

constructor chaining

In the last lecture we discussed overloaded methods and constructors. Note that one overloaded constructor may chain to another, i.e. call it. For example, the second Point constructor below chains to the first:

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

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

}

The syntax ": this(args)" indicates that a constructor should call another constructor in the same class, passing it the given arguments. A chaining constructor may also have a body, which contains code that will run after the call to the other constructor returns. (In this particular example, the body is empty.)

static members

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

class Person {
    string name;
    int id;

    static int next_id;

    public Person(string name) {
        this.name = name;
        this.id = next_id++;
    }

    public static int get_next_id() {
        return next_id;
    }
}

In this class the static field next_id holds the next id that will be assigned to a newly created Person. A static method such as get_next_id() is invoked on a class, not on an instance:

WriteLine(Person.get_next_id());

Sometimes we may reasonably implement a method either as an instance method or a static method. For example:

class Point {
    double x, y;

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

    public double dist(Point p) =>
        Sqrt((p.x - this.x) * (p.x - this.x) +
             (p.y - this.y) * (p.y - this.y));

    public static double dist(Point p, Point q) =>
        Sqrt((p.x - q.x) * (p.x - q.x) +
             (p.y - q.y) * (p.y - q.y));

}

Thie class has two overloaded versions of a dist() method that computes the distance between two points. The first is an instance method, and can be invoked like this:

Point p = new Point(3, 4);
Point q = new Point(5, 6);

WriteLine(p.dist(q));

The second is a static method, and can be invoked like this:

WriteLine(Point.dist(p, q));

Notice that either an instance method or static method may access private fields of any instance of the class that it belongs to.

Which version of dist() is better? This is a question of style. Some people may prefer the static version because it is more symmetric.

static classes

A class may be marked static to indicate that all of its members are static. For example, we might use a static class to hold numeric utility methods:

static class NumUtil {
    static int gcd(int a, int b) =>
        a == 0 ? b : gcd(b, a % b);

    static bool coprime(int a, int b) =>
        gcd(a, b) == 1;
}

No instance of a static class may ever exist. If you try to create one using the new() operator, you'll receive a compile-time error.

It may be helpful to import a static class with 'using static' so that you can access its members without specifying a prefix:

using static NumUtil;

properties

A property is syntactically like a field, but acts like a method. Specifically, it contains a getter and/or a setter, which are blocks of code that run when the caller retrieves or updates the property's value.

As an example, here's a class that implements a stack of integers:

class IntStack {
    int[] a;
    int n;

    public IntStack(int limit) {
        a = new int[limit];
    }

    public bool is_empty {
        get {
            return n == 0;
        }
    }

    public int limit {
        get {
            return a.Length;
        }
        set {
            Debug.Assert(value >= n, "new limit is too small");
            int[] b = new int[value];
            for (int i = 0; i < n; ++i)
                b[i] = a[i];
            a = b;
        }
    }

    public void push(int i) {
        a[n++] = i;
    }

    public int pop() {
        return a[--n];
    }
}

The class has a boolean property is_empty that's true if the stack is empty. This property has a getter but no setter, so it can only be read. For example:

IntStack s = new IntStack(5);
WriteLine(s.is_empty);  // writes true

When the caller retrieves the property, the get block runs and returns a boolean value.

The class also has a property limit with both a getter and setter. We might write

IntStack s = new IntStack(5);
WriteLine(s.limit);  // writes 5
s.limit = 10;        // increase the maximum size
WriteLine(s.limit);  // writes 10

When the caller retrieves the property, the getter runs. When the caller sets the property, as in the line 's.limit = 10' above, the setter runs. Inside any setter, the keyword value refers to the value that is being set. In this specific example, it is 10.

You can use expression syntax to define getters or setters. The is_empty property above could be written as

public bool is_empty {
    get => n == 0;
}

If a property has only a getter but no setter, it can be written using an even simpler syntax:

public bool is_empty => n == 0;

indexers

An indexer allows you to define custom getter and/or setter blocks that run when an instance of your class is accessed using the array-like syntax a[i].

For example, here is a vector class that includes properties dimensions and length, plus an indexer:

class Vec {
    double[] a;

    public Vec(params double[] a) {
        this.a = a;
    }

    public int dimensions => a.Length;

    public double length {
        get {
            double t = 0.0;
            foreach (double d in a)
                t += d * d;
            return t;
        }
    }

    // indexer
    public double this[int i] {
        get {
            return a[i];
        }

        set {
            a[i] = value;
        }
    }
}

A caller can invoke this indexer as if v itself were an array:

Vector v = new Vector(2.0, 4.0, 8.0);
v[1] = 5.5;           // invokes the setter
v[2] = v[0] + 1.0;    // invokes both the getter and setter

An indexer getter may be written using expression-valued syntax, so we could simplify the get block above as follows:

get => a[i];

The indexer defined above has return type double and uses an index parameter of type int. In general, an indexer may have any return type and any index parameter type. For example, a class representing a dictionary from strings to strings might have an indexer like this:

class StringDict {
    public string this[string s] { ... }
}

An indexer may even take multiple arguments. For example, a class representing a matrix might have an indexer like this:

class Matrix {
    public double this[int r, int c] { ... }
}

overloaded operators

You may defined overloaded operators for a class, which redefine the meaning of built-in operators such as + and * when invoked on instances of the class. For example, let's add an overloaded + operator to the Vec class we saw above:

public static Vec operator + (Vec v, Vec w) {
    Debug.Assert(v.dimensions == w.dimensions, "incompatible vectors");
    double[] b = new double[v.dimensions];
    for (int i = 0 ; i < v.dimensions ; ++i)
        b[i] = v[i] + w[i];
    return new Vec(b);
}

Now we can write this:

    Vector v = new Vector(2.0, 5.0, 10.0);
    Vector w = new Vector(1.0, 3,0, 9.9);
    Vector x = v + w;

An overloaded operator must be public and static.

Notice that the overloaded operator above is invoking the class's own indexer (in the line 'b[i] = v[i] + w[i]') and also the dimensions property.

You may overload most of the built-in operators available in C#, including