This week's topics are covered in Essential C#: see ch. 3 More with Data Types (Tuples, Arrays), ch. 5 Methods and Parameters, ch. 6 Classes.
Here is a summary of the C# elements we discussed:
The conditional operator (? :
) is similar to
the if
statement, but it is an expression, not a
statement. It takes a Boolean value and two extra values. It returns
the first of these values if the boolean is true, or the second if it
is false. For example, the following statement computes the greater
of i and j and assigns it to k:
int i, j; ... int k = i > j ? i : j;
switch (i) { case 1: WriteLine("one"); break; case 2: case 3: WriteLine("two or three"); return; default: WriteLine("other"); break; }
The switch
statement is a more compact (and possibly
more efficient) alternative to a series of if statements. The default
section is optional. Each case in a switch
statement
must end with a break
or return
statement.
foreach (char c in "hello") Write(c + " ");
foreach
iterates over each element of a collection. The
only kinds of collections we have learned about so far are strings
and arrays (but we will soon see others).
A foreach
statement always declares an iteration
variable whose scope is the body of the statement.
A tuple holds two or more values. Unlike arrays, tuples have fixed length and can hold values of varying types.
A tuple type is written as a list of element types in parentheses. For example:
(int, string) p = (3, "hello");
We can access a tuple's elements using the property names Item1, Item2 and so on:
WriteLine(p.Item1); // writes 3 WriteLine(p.Item2); // writes "hello"
We can use multiple assignment to unpack a tuple into a set of variables:
(int x, string s) = p; // now x is 3, s = "hello"
The generic element names Item1
and Item2
are not very informative. Sometimes it is clearer to use a named
tuple, which specifies names
for the elements. For example:
(int x, int y) p = (10, 20);
Now we can access the tuple elements by name:
WriteLine($"x = {p.x}, y = {p.y}");
Be sure to note the difference between the syntax for multiple assignment and the syntax for creating a named tuple – these may look similar at first. The declaration above creates a single variable p with fields p.x and p.y. By contrast, the line
(int x, int y) = (10, 20);
is a multiple assigment that creates local variables x and y.
There are two kinds of multidimensional arrays: rectangular arrays and jagged arrays.
You can dynamically allocate a rectangular
array using the new
operator:
int
[,] a =
new
int
[
3
,
4
];
Or you can allocate an array and initialize it with a set of values:
int[,] a = { {1, 4, 5}, {2, 3, 6} };
You can access an
element of a rectangular array using an expression such as a[
2
,
5
]
.
An rectangular array may even have 3 or more dimensions, e.g.
int[,,] a = new int[5, 10, 15]; // a 3-dimensional array
The Length
property returns the total number of elements in a
rectangular array. This is the product of the lengths of all
dimensions. The Rank
property returns the number of dimensions, and the method
int GetLength(int d)
returns the length
of dimension d.
A jagged array is an array of arrays. Unlike a rectangular multidimensional arrays, each subarray in a jagged array can have a different length.
You can allocate a jagged array like this:
int[][] i = new int[3][]; i[0] = new int[2]; i[1] = new int[4]; i[2] = new int[5];
If you like, you can initialize a jagged array as you allocate it:
int[][] i = { new int[] { 2, 3}, new int[] { 3, 4, 5}, new int[] { 1, 3, 5, 7, 9} };
You can access an element of a jagged array using an expression such
as a[
2
][
5
]
.
A class is a user-defined data type that can contain fields, constructors, methods, and other kinds of members.
Here is a definition for a simple class implementing a fixed-size stack:
class Stack { // fields int[] a; int count; // constructor public Stack(int maxSize) { a = new int[maxSize]; count = 0; } // methods public bool isEmpty() { return count == 0; } public void push(int i) { a[count] = i; count += 1; } public int pop() { count -= 1; return a[count]; } }
Every member in a class can be either public
or private
. public
members are accessible everywhere, and private
members are accessible only within the class.
The default access level is private
.
Each instance of a class contains a set of
fields
.
A field may be declared with an initial value:
class Foo { int x = 3, y = 4; … }
If you don't specify an initial value, a field will intially be set
to its type's default value (e.g 0 for an int
).
Code in a constructor or method may refer to fields by name and may modify their values.
A constructor makes a new instance of a class. It always has the same name as its containing class.
A constructor will often intialize fields. For example:
class Point { double x, y; public Point(double x, double y) { this.x = x; this.y = y; } … }
To call a constructor, use the new
operator and pass any required arguments:
Point p = new Point(3.0, 4.0);
If a class definition includes no constructors, then C# provides a default constructor that is public and takes no arguments.
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, we could add a second constructor to the Point class that takes no arguments, and constructs a Point at the origin (0,0):
public Point() : this(0.0, 0.0) { }
this
is a special value that refers to the object on which a constructor
or method was invoked.
A method is like a function, but belongs to a class. In a method declaration, the return type precedes the method name:
int mul(int x, int y) { return x * y; }
You may declare multiple methods that have the same name but have different numbers and/or types of parameters. This is called method overloading. For example:
int abc(int i) { return i + 1; } int abc(int i, int j) { return i + j; } string abc(string s) { return s + s; }
If a method’s body is a simple expression, the method can be defined using a compact syntax:
int mul(int x, int y) => x * y;
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;
Every type in C# is either a value type or a reference type.
value types: all numeric types, bool
, char
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
At
the top of a source file, you can use a using
static
declaration
to import all static methods from a given class, so that you can use
these methods without prefixing with them with the class name. For
example, if you write
using static System.Math;
then your code will be able to call Sqrt()
and Exp()
directly without having to write Math.Sqrt()
and
Math.Exp()
. This is often convenient.