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
Every type in C# is either a value type or a reference type.
value types: all numeric types, bool
,
char
, tuples, nullable types, enums
reference types: string
, arrays,
classes
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
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.
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.)
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.
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;
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;
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] { ... } }
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
unary operators: +
, -
,
!
, ++
, –
binary operators: +, -, *, /, %, <, >, <=, >=