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.

table of contents


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


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


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


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


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.

verbatim strings

Strings may normally contain the escape sequences described above. But you may prefix a string with @ to create a verbatim string in which the backslash is an ordinary character. A verbatim string may even contain newlines:




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 int i = 1357;
    const double pi = 3.14159;

    WriteLine($"{i:d6} {pi:f2} {i:n0}");


    001357 3.14 1,357


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

You can initialize a multidimensional array as you allocate it:

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

Once again, in a variable declaration 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];

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}


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


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, use an explicit cast or access the Value property. These are equivalent, and will fail with an exception at run time if the value is null.

    int? a = xyz();
    int b = (int) a;
    int c = a.Value;  // equivalent


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.


equality operators

Note that when comparing values of reference type, by default these operators use reference equality: they return true only if two objects are actually the same object, and do not consider objects' fields. For example:

    class Foo {
      int i;
      public Foo(int i) { this.i = i; }

    WriteLine(new Foo(3) == new Foo(3));  // writes False

(A particular class may overload these operators to test equality in some different way.)

relational operators

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 and null-conditional operators

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

The null-conditional operator ?. operates on a value of any nullable type, including reference types. It invokes a method if the value is non-null. If the value is null, the operator returns null. For example:

    string s = abc();
    string t = s?.Trim();

It is often convenient to chain method calls using this operator:

    string t = s?.Trim()?.ToLower();


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
    Stack s = getStack();
    if (s is LinkedStack)

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

    if (abc() is int i)

Here is a loop using is:

    while (ReadLine() is string 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.


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


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



    if (i > 0)
    else if (i == 0)

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 (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 (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 (char c in "hello")
      Write(c + " ");

foreach iterates over each element of a string or array. (Soon, we will see other types that we can traverse using foreach.) A foreach statement always declares an iteration variable whose scope is the body of the statement.


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


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


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

      n += 1;

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


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

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


    switch (i) {
      case 1:
      case 2:

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 statement.


      i += 1;
      if (i < 10)
        goto again;

The goto statement jumps to another point in a block of code. You should rarely if ever use it.


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;


A local variable may optionally have an initial value:

    int i = 3, j = 4;

A local constant is like a local variable, but is fixed at compile time:

    const int Million = 1_000_000;

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;

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.

For any types T and U, C# considers T to be more specific than U if there is an implicit conversion from T to U, but none from to U.

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


A class is an abstract data type that can contain fields, constructors, methods, properties, indexers, overloaded operators 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 or private. (There are additional access levels that we will learn about soon.) public members are visible outside the class; private members can be used 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 any method, constructor or other member 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.

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 is a special value that refers to the object on which a method, constructor or other member was invoked.


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


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 AvgStack : LinkedStack {
  int count;
  int sum;
  public override void push(int i) {
    count += 1;
    sum += i;
  public override int pop() {
    int i = base.pop();
    count -= 1;
    sum -= i;
    return i;
  public double average {
    get => (double) sum / count;

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 AvgStack();
    AvgStack t = (AvgStack) s;
    WriteLine(t.average);  //  writes 5

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

overriding ToString

For any class, you may define a ToString method that converts an instance of the class to a string. For example:

    public override string ToString() => $"({x}, {y})";

(ToString is a method of the top-level object class, which we will learn about in an upcoming lecture.)

static members

A field, constructor, method, property or indexer 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:


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 {


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

explicit member implementation

Usually a class implements interface methods (and other members) implicitly, as in the LinkedStack example above. An implicit member implementation includes the keyword public and is visible through variables of either the class type or the interface type:

LinkedStack ls = new LinkedStack();
Stack s = ls;

Alternatively, a class may implement a member explicitly. An explicit member implementation is prefixed with the interface name and may not be public:

class AlterStack : Stack {

  void Stack.push(int i) {  }

An explicit member implementation is visbile only through a variable of the interface type, not the class type:

AlterStack a = new AlterStack();
a.push(3);  // invalid - compile error

Stack b = new AlterStack();
b.push(3);  // works fine

Explicit member implementations are useful when a class implements two interfaces and a member with the same name occurs in both interfaces, i.e. when there is a name conflict. In this situation, a class can provide a separate explicit implementation for each interface. But we will not often (if ever) encounter this situation in this course.


Structs are similar to classes, but they are value types, not reference types. Here is a simple struct:

struct Point {
  public double x, y;
  public Point(double x, double y) { this.x = x; this.y = y; }
  public static double distance(Point p, Point q) =>
    Sqrt((p.x - q.x) * 2 + (p.y - q.y) * 2);

Structs have some limitations:

It is sometimes more efficient to use structs than classes, since a variable of struct type does not contain an implicit pointer. On the other hand, each time a struct is passed as a method argument, it is copied, which is more expensive than passing a class reference by value. So in some situations structs are less efficient.

I generally recommend that you use structs only for data types that contain just a few values such as integers or doubles. For example, types that represent rational numbers or complex numbers would be good candidates for structs.


Types in the class library are grouped into namespaces. For example:

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.IO;

then it may refer to StreamReader without any namespace prefix.

top-level programs

    using static System.Console;

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