C# includes two operators for preincrementing or postincrementing a value:
++x (preincrement)
x++ (postincrement)
When you use these operators as statements, they are equivalent:
int x = 4; ++x; // now x is 5 x++; // now x is 6
However when you use them inside an expression they behave differently. The value of the expression (x++) is the value of x before it is incremented. Similarly, the value of (++x) is the value of x after it is incremented:
int x = 4; int y = x++; // now x is 5, and y is 4 int z = ++x; // now x is 6, and z is 6
It's common to use one of these operators as the iterator in a for
loop:
for (int i = 0 ; i < 10 ; ++i) Console.WriteLine(i);
In this situation the decision whether to use "++i
"
or "i++
" is a question of style, since they
are equivalent.
You can make a method parameter optional by giving it a default value:
void fill(int[] a, int start = 0, int val = -1) { for (int i = start ; i < a.Length ; ++i) a[i] = val; }
The caller can omit any optional parameters:
int[] a = new int[10]; fill(a); // same as fill(a, 0, -1) fill(a, 5); // same as fill(a, 5, -1) fill(a, 7, 3);
Optional parameters must appear after any non-optional parameters in a parameter list.
When you invoke any method, you may precede any argument with a parameter name, followed by a colon.
For example, the Substring
method in the string
class is defined as
public string Substring (int startIndex, int length);
We may invoke it in any of these ways:
string s = "bowling ball"; string t = s.Substring(1, 3); // "owl" string u = s.Substring(startIndex: 1, length: 3); // identical string v = s.Substring(length: 3, startIndex: 1); // identical
Notice that named arguments may appear in any order, and must appear after any non-named arguments in an argument list.
If some parameters are optional, you may specify
any subset of them you like using named arguments. For example, we
may invoke the fill
function from the previous section
as
fill(a, val: 4) // same as fill(a, 0, 4)
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
In a class you may declare multiple methods or constructors that have the same name but have different numbers and/or types of parameters. This is called overloading.
For example, here's a class that implements a vector with any number of dimensions:
class Vec { double[] a; public Vec(params double[] a) { this.a = a; } public Vec(int dims) { this.a = new double[dims]; } // Compute the sum of this vector and the vector w. public Vec add(Vec w) { double[] b = new double[a.Length]; for (int i = 0 ; i < a.Length ; ++i) b[i] = this.a[i] + w.a[i]; return new Vec(b); } }
Notice that there are two overloaded constructors: one takes any number of parameters representing coordinates, and the other creates a zero vector of any number of dimensions. Let's create two vectors using these constructors:
Vec v = new Vec(2.0, 5.0, 10.0); Vec w = new Vec(5); // 5-dimensional zero vector
Notice that if the second overloaded constructor above did not exist,
then the call new
Vec(
5
)
would call the first constructor, since 5 can be implicitly
converted to a double. When both constructors exist, the call new
Vec(
5
)
is potentially ambiguous, but C# resolves the ambiguity by
choosing the constructor that does not require an implicit
conversion.
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 as 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 Util { 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 Util;
A class may contain constant members. For example:
class Stack { const int LIMIT = 100; int[] a = new a[LIMIT]; ... }
Like other members, a constant may be either public or private.
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 isEmpty { 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 isEmpty
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.isEmpty); // 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 isEmpty
property above could be written as
public bool isEmpty { get => n == 0; }
If a property has only a getter but no setter, it can be written using an even simpler syntax:
public bool isEmpty => 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 dims
and length
, plus an
indexer:
class Vec { double[] a; public Vec(params double[] a) { this.a = a; } public int dims => a.Length; // number of dimensions 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:
Vec v = new(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.dims == w.dims, "incompatible vectors"); double[] b = new double[v.dims]; for (int i = 0 ; i < v.dims ; ++i) b[i] = v[i] + w[i]; return new Vec(b); }
Now we can write this:
Vec v = new(2.0, 5.0, 10.0); Vec w = new(1.0, 3,0, 9.9); Vec 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 dims
property.
You may overload most of the built-in operators available in C#, including
unary operators: +
, -
,
!
, ++
, –
binary operators: +, -, *, /, %, <, >, <=, >=