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.
A switch statement is a more compact
(and possibly more efficient) alternative to a series of if
statements. It looks like this:
switch (i) {
case 11:
WriteLine("jack");
break;
case 12:
WriteLine("queen");
break;
case 13:
WriteLine("king");
break;
case 1:
case 14:
WriteLine("ace");
break;
default:
WriteLine(i);
break;
}In this example, the group of statements that matches the value of (i) will run. For example, if i is 12, the code will print "queen". If it's either 1 or 14, it will print "ace".
Each section in a switch statement
must end with a break or return statement.
The default section is optional, and runs if no other
case is matched.
C# also includes switch expressions.
Here's an example:
string name = i switch {
11 => "jack",
12 => "queen",
13 => "king",
1 or 14 => "ace",
_ => i.ToString()
};
Notice that a switch expression is more compact than a
switch statement. In it, an underscore (_) represents a
default case.
Actually switch statements and
expressions can do a lot more than this – for example, they can
choose a case based on the type of a value, and can match
patterns. We may discuss these capabilities later in this course.
In many programs we may wish to access command-line arguments.
In a new-style
program with no main class or Main()
method, the command-line arguments
are accessible via a predefined variable args
of type string[].
For example, here is a one-line program:
Console.WriteLine($"Hello, {args[0]}.");Let's run it:
$ dotnet run Fred Hello, Fred.
If a C# program has a main class, then the Main() method
may take no arguments (as we have seen before), or may take an array
of command-line arguments:
static void Main(string[] args) {
...For example, here's a program that echoes each argument to the output:
class Top {
static void Main(string[] args) {
foreach (string s in args)
Console.WriteLine(s);
}
}Let's try it:
$ dotnet run one two three one two three $
Notice that (unlike in Python) the name of the program is not present
in the args[] array.
We may use the StreamReader and
StreamWriter classes in the System.IO
namespace to read from and write to files. You can create a
StreamReader or StreamWriter using the new
operator, passing a filename. For example:
StreamReader sr = new("myfile.txt");
If you want to read all lines from a file, you don't actually need to
use a StreamReader. As we saw in an earlier lecture,
you can just use File.ReadLines():
int sum = 0;
foreach (string line in File.ReadLines("nums.txt"))
sum += int.Parse(line);When you are finished with a StreamReader
or StreamWriter, you should close it. You can call the
Close() method for this purpose. It
is especially important to close a StreamWriter
-
if you do not, some output may not be written!
C#
includes a feature that makes it easy
to
close files (or other
resources)
automatically.
When you declare any variable,
you
may precede it with the using
keyword.
Then the object in that variable will automatically be freed as soon
as code exits the block of code containing the variable declaration
(and
even if it exits via an exception that was thrown).
For example:
using StreamWriter sr = new(filename);
This feature may remind you of Python's with statement,
which has a similar purpose.
Using the ideas above, we can write the classic
Unix utility cp, which copies one file to another:
if(args.Length!=2){Console.WriteLine("usage: dotnet run <from> <to>");return;}stringfrom=args[0],to=args[1];usingStreamWritersw=new(to);foreach(stringlineinFile.ReadLines(from))sw.WriteLine(line);
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 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 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: +, -, *, /, %, <, >, <=, >=