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, 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) { } … }
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;
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
A property is syntactically like a field,
but contains a getter and/or a setter, which are
methods that run when the caller retrieves or updates the property's
value. Inside the setter, the keyword value
refers to
the value that is being set.
Here is a partial listing of a class Vector
that includes a property length
:
class Vector { double[] a; ... public int length { get { return a.Length; } set { a = new double[value]; } } }
You can use expression syntax to define getters or setters. The
length
property above could be written as
public int length { get => a.Length; set => a = new double[value]; }
An indexer allows you to define custom
getter and/or setter methods that run when an instance of your class
is accessed using the array-like syntax a[i]
. For
example, we can extend the Vector class above with an indexer that
retrieves and sets elements of the underlying array v:
public double this[int i] { get { return a[i]; } set { a[i] = value; } }
A caller can now invoke this indexer as if v itself were an array:
Vector v = new Vector(...); v[3] = 77.2; v[4] = v[5] + 1.0;
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.
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:
class Vector { double[] a; public Vector(params double[] a) { this.a = a; } public static Vector operator + (Vector v, Vector w) { double[] b = new double[v.a.Length]; for (int i = 0 ; i < v.a.Length ; ++i) b[i] = v.a[i] + w.a[i]; return new Vector(b); } }
The operator above can be invoked like 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
.
You may overload most of the built-in operators available in C#, including
unary operators: +
, -
,
!
, ++
, –
binary operators: +, -, *, /, %, <, >, <=, >=