C# Quick Reference

This is a summary of the subset of C# that we have learned so far in Programming II. For more details on any of the features below, see the Essential C# textbook or the C# reference pages.

comments

A single-line comment begins with // and extends to the end of the line:

x += 1;    // increment x 

Comments delimited with /* and */ can extend over multiple lines:

/*
  this is a comment with
  multiple lines
*/

types

Each short type name in C# abbreviates a longer name. For example, int is the same as System.Int32. You can see the full names in the Library Quick Reference. You can use the shorter and longer names interchangeably.

integer types

A literal integer (or floating-point value) may contain embedded underscores for readability:

    int i = 1_000_000_000;    // 1 billion

floating-point types

bool

The bool type represents a Boolean value, namely either true or false.

char

A char is a 16-bit Unicode character.

A character constant is enclosed in single quotes, e.g. 'x' or 'ř'. A character constant containing a backslash is an escape sequence. Here are some common escape sequences:

To create a character constant representing a single quote or backslash, use one of the sequences above:

    WriteLine('\\');  // writes a backslash
    WriteLine('\'');  // writes a single quote

string

A string is an immutable string of characters. A string constant is enclosed in double quotes, e.g. "hello".

You may access individual characters of a string using square brackets:

    string s = "spire";

    char c = s[2];    // now c = 'i'

Note that characters are indexed starting from 0.

interpolated values

A string beginning with $ may contain interpolated values enclosed in braces:

    int a = 3, b = 4;

    WriteLine($"{a} plus {b} equals {a + b}");

A interpolated value may be followed by a format specifier (preceded by a colon).

There are many predefined format specifiers. Many format specifiers can be followed by an integer called the precision, whose meaning varies among specifiers. Here are a few of the most useful:

For example,

    const double pi = 3.14159;
    const int i = 1357;
    
    WriteLine($"{pi:f2} {i:n0}");

writes

    3.14 1,357

arrays

You can allocate an array like this:

    int[] i = new int[10];

Arrays are indexed from 0.

You can allocate and initialize an array at the same time:

    i = new int[3] { 3, 4, 5 };

If you allocate and initialize an array as part of a variable declaration, you can skip the new operator:

    int[] i = { 3, 4, 5 };

The Length property returns the length of an array.

multidimensional arrays

You can similarly allocate a multidimensional array using the new operator:

    int[,] i = new int[3, 4];

Once again, in an array declaration with initial values you can skip the new operator:

    int[,] i = { {1, 4}, {2, 3} };

For a multidimensional array, the Length property returns the total number of elements in the array. This is the product of the lengths of all dimensions. Also see Rank and GetLength in the Library Quick Reference.

jagged arrays

A jagged array is an array of arrays. This is not the same thing as a multidimensional array. A multidimensional array has a rectangular shape, whereas each array 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];

Now you can get or set elements like this:

i[0][0] = 2;
i[0][1] = 3;
i[1][0] = i[0][0] + 1;

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}
    };

enums

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;

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

value and reference types

Every type in C# is either a value type or a reference type.

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 pointer to an object. An assignment between variables of reference type makes them point to the same object.

For example:

    int[] a = { 4, 5, 6 };
    int[] b = a;       // now b and a point to the same array
    a[1] = 7;
    WriteLine(b[1]);   // writes 7

null

Any variable of reference type may hold the special value null.

Note especially that null is not the same as the empty string.

nullable types

Value types such as int and bool cannot hold null. However, each such type can be made nullable by appending a ? character to its name. A nullable type holds either an instance of its base type, or the value null. For example:

    int? x = 7;
    if (x > 3)
       x = null;

A value of the base type can be converted to a nullable type implicitly:

    int y = abc();
    int? z = y;

To convert from a nullable type to its base type, you can use an explicit cast. This will fail with an exception at run time if the value is null.

    int? a = xyz();
    int b = (int) a;

subtypes

In some situations a class may be a subtype of another type:

For example:

    interface Collection {  }
    interface Stack : Collection {  }
    class SimpleStack : Stack {  }
    class HyperStack : SimpleStack {  }

Here, HyperStack is a subtype of SimpleStack, which is a subtype of Stack, which is a subtype of Collection.

A type may be implicitly converted to any supertype:

    Collection c = new HyperStack();  // implicit conversion from HyperStack to Collection

A type may be explicitly converted to any subtype:

    HyperStack h = (HyperStack) c;    // explicit conversion from Collection to HyperStack

This explicit conversion will fail at runtime if the object in question does not actually belong to the subtype.

operators

equality operators

relational operators

These operators are defined on numbers and characters, but not on strings.

arithmetic binary operators

assignment operator

An assignment is actually an expression in C#:

          y = (x = 4) + 2;  // assign 4 to x and 6 to y

compound assignment operators

boolean operators

conditional operator

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);

increment/decrement operators

The increment operator ++ increases a variable or field by 1. It comes in two forms. With the pre-increment operator, the expression's value is the value of the variable after it is incremented:

    int i = 4;
    int j = ++i;  // now i = 5 and j = 5

With the post-increment operator, the expression's value is the value of the variable before it is incremented:

    int i = 4;
    int j = i++;  // now i = 5 and j = 4

The decrement operator -- also has pre-decrement and post-decrement forms, and works similarly.

null-coalescing operator

The null-coalescing operator ?? operates on a value of any nullable type, including reference types. If the value is null, the operator returns its second operand; otherwise the value itself is returned. For example:

    int? x = abc();
    
    int y = x ?? 0;
    int z = (x == null) ? 0 : x.Value;  // equivalent

is

The is operator returns true if a value belongs to a type. It works with both nullable types and reference types:

    int? i = abc();
    if (i is int)  // true if i != null
      WriteLine(i.Value);
    
    Stack s = getStack();
    if (s is LinkedStack)
      WriteLine("linked");

The is operator can optionally bind a variable. The first example above can be rewritten as

    if (abc() is int i)
        WriteLine(i);

Here is a loop using is:

    while (ReadLine() is string s)
        WriteLine(s);

In this loop, when ReadLine() returns a non-null value, the string variable s receives that value and the loop continues. When ReadLine() returns null, the loop terminates.

as

The as operator checks whether a value belongs to a type. If so, it returns the value; otherwise it returns null:

    Stack s = getStack();
    LinkedStack ls = s as LinkedStack;  // if s was not a LinkedStack, ls will be null

default

The default operator returns the default value for a type:

    WriteLine(default(int));   // writes 0

default is most useful inside a generic class, where it can act on a type parameter. For example, a dynamic array class could have this code:

class DynArray<T> {
  T[] a;
  int count;

  

  public T this[int index] {
    // return default value if out of bounds
    get => index < count ? a[index] : default(T);

    set => 
  }
}

conversions

C# will perform an implicit conversion between two numeric types if every possible value of the source type is valid in the destination type. For example:

    short s;
    ...
    int i = s;   // implicit conversion

You can also implicitly convert a char to any numeric type that can hold a 16-bit unsigned value:

    char c = 'ř';
    int i = c;   // now i holds the Unicode value for 'ř', i.e. 345

You can use an explicit conversion to force a conversion between any two numeric types. This is accomplished with a type cast:

    int i = 1000;
    short s = (short) i;

If the destination type cannot hold the source value, it will wrap around or be truncated.

You can explicitly convert any numeric type to a char:

    int i = 100;
    char c = (char) i;  // now c is 'd', which is ASCII/Unicode character 100

statements

local variable declarations

A local variable declaration declares one or more local variables and optionally gives them initial values:

    int i = 3, j = 4;

Implicitly typed variables

When you declare a local variable, you can use the var keyword to tell the compiler to infer its type. For example:

    var list = new List<int>(5);

This is equivalent to

    List<int> list = new List<int>(5);

if

    if (i > 0)
      WriteLine("positive");
    else if (i == 0)
      WriteLine("zero");
    else
      WriteLine("negative");

An if statement executes a statement (or block) if the given value is true. If the statement has an else clause, it is executed if the given value is false.

while

    while (i < 10) {
      sum = sum + i;
      i += 1;
    }

A while loop loops as long as the given condition is true.

do / while

    do {
      s = ReadLine();
    } while (s != "yes" && s != "no");

A do/while loop is like a while loop, but checks the loop condition at the bottom of the loop body.

for

    for (int i = 0, j = 0 ; i < 10 ; i += 1, j += 2)
      WriteLine(i + j);

A for statement contains three clauses, separated by semicolons, plus a loop body.

foreach

    int[] a = { 4, 5, 6 };

    foreach (int i in a)
      Write(i);

foreach iterates over each element of a string or array. A foreach statement always declares an iteration variable whose scope is the body of the statement.

break

    for (int i = 1 ; i <= 10 ; ++i) {
      if (n % i == 0)
        break;

      WriteLine(i);
    }

A break statement breaks out of the nearest enclosing while, do/while, for or foreach loop.

continue

    while (i < 100) {
      if (n % i == 0)
        continue;

      WriteLine(i);
      n += 1;
    }

A continue statement continues with the next iteration of the nearest enclosing while, do/while, for or foreach loop.

return

    static int add(int x, int y) {
      return x + y;
    }

The return statement returns a value immediately from the enclosing method.

switch

    switch (i) {
      case 1:
        WriteLine("one");
        break;
        
      case 2:
      case 3:
        WriteLine("two or three");
        break;
      
      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.

methods

A method is like a function, but belongs to a class. In a method declaration, the return type precedes the method name:

    static int mul(int x, int y) {
      return x * y;
    }

expression-bodied methods

If a method’s body is a simple expression, the method can be defined using a compact syntax:

    static int mul(int x, int y) => x * y;

overloaded methods

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:

  static int more(int i) => i + 1

  static int more(short s) => s + 2
  
  static string more(string s) => s + " "

When calling an overloaded method, sometimes there is more than one candidate method. For example, consider this method call:

  byte b = 77;
  WriteLine(more(b));

Both of the first two method declarations above are candidates for this call, since byte is implicitly convertible to both int and short. In this situation, C# will favor the overload that involves converting to a more specific type. short is more specific than int, so in the example above C# will call the method

  int more(short s)

and will write the value 79.

ref and out parameters

A parameter marked with ref is passed by reference: the method can modify the variable that is passed. Here's a method that swaps two variables:

    static void swap(ref int a, ref int b) {
      int t = a;
      a = b;
      b = t;
    }

You must include the keyword ref when passing an argument by reference:

    int i = 3, j = 4;
    swap(ref i, ref j);

A parameter marked with out returns a value to the caller. This method takes two integers and returns both their sum and product:

    static void sumAndProduct(int a, int b, out int sum, out int product) {
      sum = a + b;
      product = a * b;
    }

You must include the keyword out when passing a variable to an out parameter:

    int s, p;
    sumAndProduct(3, 4, out s, out p);

You can declare a new variable as you invoke a method with an out parameter. The following is equivalent to the two preceding lines:

    sumAndProduct(3, 4, out int s, out int p);

parameter arrays (params)

If the last parameter to a method is marked with params and has type T[], then in its place the method can receive any number of arguments of type T. The caller may pass individual values separated by commas, or may pass an array of type T[].

For example, this method receives a parameter array of integers and returns their sum:

    static int sum(params int[] a) {
      int s = 0;
      foreach (int i in a)
        s += i;
      return s;
    }

It can be invoked as

    int s = sum(4, 5, 6);

Alternatively, the caller can pass an array:

    int[] a = { 4, 5, 6 };
    int s = sum(a);

generic methods

A method may be generic: it may take one or more type parameters. For example:

public static void swap<T>(ref T a, ref T b) {
    T t = a;
    a = b;
    b = t;
  }

generic method constraints

A method's type parameters may have constraints. For example:

public static void copy<T, U>(T[] a, U[] b)
                   where T : U {
    for (int i = 0 ; i < a.Length ; ++i)
      b[i] = a[i];
  }

For details about the types of constraints that are allowed, see the section "Generic class constraints", below.

classes

A class is an abstract data type that can contain fields, constructors, methods, properties and other kinds of members.

Here is a definition for a simple class representing a point in two dimensions. It has two fields, one constructor and two methods:

using static System.Math;

class Point {
  double x, y;
  
  public Point(double x, double y) { this.x = x; this.y = y; }
  
  public void move(double dx, double dy) {
    x += dx;
    y += dy;
  }

  public double distanceFromOrigin() {
    return Sqrt(x * x + y * y);
  }
}

access levels

Every member in a class can be either public, protected or private.

The default access level is private.

fields

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 any method, constructor or other member may refer to fields by name and may modify their values.

constructors

A constructor makes a new instance of a class. It always has the same name as its containing class.

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 takes no arguments.

Constructors may be overloaded. A constructor may call another constructor; this is called constructor chaining. For example, we could add a second constructor to the Point class that chains to the constructor with two parameters:

    public Point(double x) : this(x, 0.0) {  }

instance methods

An instance method is invoked on an instance of a class. Any additional arguments appear between parentheses:

    Point p = new Point(3.0, 4.0);  // a constructor call
    p.move(1.0, 2.0);               // a method call

For more information about methods, see the Methods section above.

this

this is a special value that refers to the object on which a method, constructor or other member was invoked.

properties

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[] v;
  ...
  
  public int length {
    get {
      return v.Length;
    }
    set {
      v = 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 => v.Length;

      set => v = new double[value];
    }

indexers

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 v[i]; }
        set { v[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.

overloaded operators

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 Foo {
      int i;
  
      public Foo(int i) { this.i = i; }
  
      public static Foo operator + (Foo a, Foo b) =>
        new Foo(a.i + b.i);
    }

The operator above can be invoked like this:

    Foo f = new Foo(3);
    Foo g = new Foo(4);
    Foo h = f + g;

An overloaded operator must be public and static.

You may overload most of the built-in operators available in C#, including

class inheritance

A class may inherit from another class. For example:

class LinkedStack : Stack {
  Node head;
  
  public virtual void push(int i) { ... }
  public virtual int pop() { ... }
  public bool isEmpty { ... }
}

class LimitStack : LinkedStack {
  protected int count;
  protected int limit;
  
  public LimitStack(int limit) { this.limit = limit; }

  public override void push(int i) {
    if (count == limit) {
      WriteLine("stack is full");
      return;
    }
    ++count;
    base.push(i);
  }
  
  public override int pop() {
    --count;
    return base.pop();
  }
}

class TwoLimitStack : LimitStack {
  int threshold;
  
  public TwoLimitStack(int threshold, int limit) : base(limit) {
    this.threshold = threshold;
  }
  
  public override void push(int i) {
    if (count == limit - threshold)
      WriteLine("warning: stack is getting full");
    base.push(i);
  }
}

A child class constructor can invoke a base class constructor using the base keyword. In the example above, the TwoLimitStack constructor calls the LimitStack constructor in this way.

An child class may override methods, properties or indexers in its base class. Only a member marked as virtual can be overridden. Any overriding member must include the override keyword.

A method in a child class can invoke a base class method using the base keyword as in the example above.

Note that an object's behavior is determined entirely by its actual class, not by the type of any containing variable. For example:

    LinkedStack s = new LimitStack(5);
    s.push(4);
    s.push(6);

In this example, the calls to the push method invoke the implementation in LimitStack, even though the containing variable has type LinkedStack.

Note that C# supports only single class inheritance: a class may inherit from only one other class.

abstract classes and methods

A class marked abstract cannot be instantiated using the new operator; only subclasses of it can be instantiated. Abstact classes may have abstract methods, which have a method signature but no body. Any concrete (non-abstract) subclasses of an abstract class must provide an implementation for every abstract method. Any abstract method is implicitly virtual.

Here is an example of an abstract class that maintains a count of objects in a stack. Any concrete subclass (for example, a class implementing a stack using a linked list) must provide implementations of the push2 and pop2 methods.

abstract class CountingStack {
  int _count;
  
  protected abstract void push2(int i);
  protected abstract int pop2();
  
  public void push(int i) {
    ++_count;
    push2(i);
  }
  
  public int pop() {
    --_count;
    return pop2();
  }
  
  public bool isEmpty { get => (_count == 0); }
  
  public int count { get => _count; }
}

generic classes

A class may be generic, taking one or more type parameters:

class Pair<T> {
  public T first, second;
  
  public Pair(T first, T second) {
    this.first = first; this.second = second;
  }
}

generic class constraints

A class's type parameters may include constraints. For example:

class Maximizer<T> where T : IComparable<T> {
  T _max;
  bool empty;
  
  public void add(T t) {
    if (empty || t.CompareTo(_max) > 0)
      _max = t;
    empty = false;
  }
  
  public T max { get => _max; }
}

static members

A field, constructor, method, or property may be static. Static members are shared by all instances of a class.

    class Foo {
      static int x = 1;
      static int y;

      static Foo() {
          y = x + 1;
      }
   
      static void inc() { x += 1; }
    }

A static constructor always takes no arguments. It cannot be invoked explicitly, and runs automatically when a class is accessed for the first time.

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;

You may mark a class itself as static to indicate that it may only have static members; there can be no instances of such a class. For example, we could declare the class Foo above as

    static class Foo {
      ...

interfaces

An interface defines a set of methods, properties and/or indexers that can be implemented by a class. For example:

interface Stack {
  bool isEmpty { get; }
  void push(int i);
  int pop();
}

A class may implement one or more interfaces:

class LinkedStack : Stack {
  Node head;

  public bool isEmpty { get => (head == null); }
  
  public void push(int i) {
    head = new Node(i, head);
  }
  
  public int pop() {
    ...
  }
}

An interface may inherit from one or more other interfaces. For example, we can refactor the Stack interface above as follows:

interface Collection {
  bool isEmpty { get; }
}

interface Stack : Collection {
  void push(int i);
  int pop();
}

interface Queue : Collection {
  void enqueue(int i);
  int dequeue();
}

generic interfaces

An interface may be generic, taking one or more type parameters. For example:

interface Map<K, V> {
  bool get(K key, out V val);
  void set(K key, V val);
}

namespaces

Types in the standard library are grouped into namespaces. For example, in the type System.Int32, System is a namespace and Int32 is a type name.

The using statement must appear at the top of a source file. It imports types from a namespace, allowing you to use them without specifying a namespace every time. For example, if a source file includes

   using System;

then you may refer the type System.Console as just Console, i.e. without any namespace prefix.

You may also use using static to import all static methods from a class, so that you can call them without specifying a class name. For example, if a source file includes

    using static System.Console;

then you can write just WriteLine() or ReadLine() to call the static methods Console.WriteLine() and Console.ReadLine().

top-level program

using static System.Console;

class Hello {
  static void Main() {
    WriteLine("hello, world");
  }
}