Reading 2: Basic C#

Credits: This reading is a port from Java to C# from 6.005 — Software Construction on MIT OpenCourseWare . Click here to see the original reading based on Java.

Objectives

  • Learn basic C# syntax and semantics

  • Transition from writing Python to writing C#


Recall the focus of this course!

Safe from bugs

Easy to understand

Ready for change

Correct today and correct in the unknown future.

Communicating clearly with future programmers, including future you.

Designed to accommodate change without rewriting.

Getting started with C# Tutorials

The next few sections link to the C# Tutorials to get you up to speed with the basics.

You may also find this C# tutorial helpful as an alternative resource.

This reading and other resources will frequently refer you to the .NET API documentation which describes all the classes built in to C#. The .NET Framework provides lots of features which can be used with a variety of languages – not just C#. For example, the .NET framework defines in its System module a class called Console which have a method WriteLine that outputs text to the console. We’ve already seen Console in our HelloWorld program!

Language basics

Read C# Program Structure and C# Basic Syntax. Try running these programs yourself. For example, create a file hello.cs that contains your C# program. At the command line, compile and run as follows:

$ csc hello.cs

$ mono hello.exe

Types and variables

Read about Types, Type conversions, Variables, Constants, Arrays, and Strings.

Operators, expressions, and control flow

Read about Operators, Decision Making, and Loops.

Classes and objects

Read about Encapsulation, Methods, Classes, Inheritance, and Polymorphism.


Snapshot diagrams

It will be useful for us to draw pictures of what’s happening at runtime, in order to understand subtle questions. Snapshot diagrams represent the internal state of a program at runtime – its stack (methods in progress and their local variables) and its heap (objects that currently exist).

Snapshot diagrams are similar to function stack diagrams in your previous Swarthmore courses.

Recall why function/snapshot diagrams help us:

  • To talk to each other through pictures (in class and in team meetings)

  • To illustrate concepts like primitive types vs. object types, immutable values vs. immutable references, pointer aliasing, stack vs. heap, abstractions vs. concrete representations.

  • To help explain your design for your team project (with each other and with your TA).

  • To pave the way for richer design notations in subsequent courses. For example, snapshot diagrams generalize into object models in 6.170.

Although the diagrams in this course use examples from C#, the notation can be applied to any modern programming language, e.g., Python, Java, JavaScript, C++, Ruby.

Primitive values

primitive values in snapshot diagram

Primitive values are represented by bare constants. The incoming arrow is a reference to the value from a variable or an object field.

Object values

object values in snapshot diagram

An object value is a circle labeled by its type. When we want to show more detail, we write field names inside it, with arrows pointing out to their values. For still more detail, the fields can include their declared types. Some people prefer to write x:int instead of int x , but both are fine.

Mutating values vs. reassigning variables

Snapshot diagrams give us a way to visualize the distinction between changing a variable and changing a value:

  • When you assign to a variable or a field, you’re changing where the variable’s arrow points. You can point it to a different value.

  • When you assign to the contents of a mutable value – such as an array or list – you’re changing references inside that value.

Reassignment and immutable values

reassigning a variable

For example, if we have a sstring variable s , we can reassign it from a value of "a" to "ab" .

String s = "a";
s = s + "b";

String is an example of an immutable type, a type whose values can never change once they have been created. Immutability (immunity from change) is a major design principle in this course, and we’ll talk much more about it in future readings.

Immutable objects (intended by their designer to always represent the same value) are denoted in a snapshot diagram by a double border, like the String objects in our diagram.

Mutable values

mutating an object

By contrast, StringBuilder (another built-in C# class) is a mutable object that represents a string of characters, and it has methods that change the value of the object:

StringBuilder sb = new StringBuilder("a");
sb.append("b");

These two snapshot diagrams look very different, which is good: the difference between mutability and immutability will play an important role in making our code safe from bugs .


C# Collections

The very first Language Basics tutorial discussed arrays , which are fixed-length containers for a sequence of objects or primitive values. C# provides a number of more powerful and flexible tools for managing collections of objects.

Lists, Sets, and Dictionaries

A C# List is similar to a Python list . A List contains an ordered collection of zero or more objects, where the same object might appear multiple times. We can add and remove items to and from the List , which will grow and shrink to accomodate its contents.

Example List operations:

C#

description

Python

int count = lst.Length;

count the number of elements

count = len(lst)

lst.Add(e);

append an element to the end

lst.append(e)

lst[0] ...

Access first element

lst[0]

In a snapshot diagram, we represent a List as an object with indices drawn as fields:

This list of cities might represent a trip from Boston to Bogotá to Barcelona.

A HashSet is an unordered collection of zero or more unique objects. Like a mathematical set or a Python set – and unlike a List – an object cannot appear in a set multiple times. Either it’s in or it’s out.

Example Set operations:

C#

description

Python

s1.Contains(e)

test if the set contains an element

e in s1

s1.IsSupersetOf(s2)

test whether s1 ⊇ s2

s1.issuperset(s2)
s1 >= s2

s1.Remove(s2)

remove s2 from s1

s1.difference_update(s2)
s1 -= s2

In a snapshot diagram, we represent a HashSet as an object with no-name fields:

Here we have a set of integers, in no particular order: 42, 1024, and -7.

A Dictionary is similar to a Python dictionary .

Example Dictionary operations:

C#

description

Python

dict[key] = val

add the mapping key → val

map[key] = val

dict[key]

get the value for a key

map[key]

dict.ContainsKey(key)

test whether the map has a key

key in map

dict.Remove(key)

delete a mapping

del map[key]

In a snapshot diagram, we represent a Dictionary as an object that contains key/value pairs:

Literals

Python provides convenient syntax for creating lists:

lst = [ "a", "b", "c" ]

And maps:

map = { "apple": 5, "banana": 7 }

C# provides a literal syntax for arrays:

string[] arr = { "a", "b", "c" };

But this creates an array , not a List However, there is similar syntax for Lists and Dictionaries:

List<string> list = new List<string>(){ "a", "b", "c" };
Dictionary<string,int> dict = new Dictionary<string,int>(){ {"a",0}, {"b",1}, {"c",2} };

Declaring List, HashSet, and Dictionary variables

Unlike Python collection types, with C# collections we can restrict the type of objects contained in the collection. When we add an item, the compiler can perform static checking to ensure we only add items of the appropriate type. Then, when we pull out an item, we are guaranteed that its type will be what we expect.

Here’s the syntax for declaring some variables to hold collections:

List<string> cities;        // a List of Strings
HashSet<int> numbers;       // a Set of Integers
Dictionary<string,Turtle> turtles; // a Map with String keys and Turtle values

Iteration

So maybe we have:

List<string> cities        = new List<int>();
HashSet<int> numbers       = new HashSet<int>();
Dictionary<string,Turtle> turtles = new Dictionary<string,Turtle>();

A very common task is iterating through our cities/numbers/turtles/etc.

In Python:

for city in cities:
    print city

for num in numbers:
    print num

for key in turtles:
    print "%s: %s" % (key, turtles[key])

C# provides a similar syntax for iterating over the items

Here’s the C#:

foreach (string city in cities) {
    Console.WriteLine(city);
}

foreach (int num in numbers) {
    Console.WriteLine(num);
}
foreach (KeyValuePair<string,Turtle> entry in turtles) {
    Console.WriteLine(entry.Key + ": " + entry.Value);
}

Under the hood this kind of for loop uses an Iterator , a design pattern we’ll see later in the class.

Iterating with indices

If you want to, C# provides different for loops that we can use to iterate through a list using its indices:

for (int ii = 0; ii < cities.size(); ii++) {
    Console.WriteLine(cities[ii]);
}

Unless we actually need the index value ii , this code is verbose and has more places for bugs to hide. Avoid.

C# API documentation

The previous section has a number of links to documentation for classes that are part of the C# .NET API.

API stands for application programming interface . If you want to program an app that talks to Facebook, Facebook publishes an API (more than one, in fact, for different languages and frameworks) you can program against. The C# API is a large set of generally useful tools for programming pretty much anything.

  • System.string is the full name of a C# string. We can create objects of type String just by using "double quotes" .

  • System.Collections.Generic.List is like a Python list, but in Python, lists are part of the language. In C#, Lists are implemented in… C#!

  • Collections.Generic.Dictionary is like a Python dictionary.

  • System.IO.File represents a file on disk. Take a look at the methods provided by File : just like with Python files, we read in all the lines of a file or process them one by one.

Let’s take a closer look at the documentation for File . There are many things here that relate to features of C# we haven’t discussed! Keep your head and focus on the things in bold below.



At the top of the page is the namespace for File . Namespaces are containers that help organize and separate code. In particular, they help us avoid naming conflicts between modules. If another system also defines a class called File, it’s ok. The namespace will help us keep track of which File we are using!

We also see a brief description, its declaration, followed by its inheritanance. File is a subclass of Object. A File object has all of the methods of Object (plus its own methods) available to use. Any subclasses would also be shown here but File doesn’t have any.

Next, we see an example showing how to use File. Below that we have a list of all available methods.

Below the summary are detailed descriptions of each method and constructor. Click a constructor or method to see the detailed description. This is the first place you should go to understand what a method does.

Each detailed description includes:

  • The method signature : we see the return type, the method name, and the parameters. We also see exceptions . For now, those usually mean errors the method can run into.

  • The full description .

  • Parameters : descriptions of the method arguments.

  • And a description of what the method returns .

Specifications

These detailed descriptions are specifications . They allow us to use tools like string , Dictionary , or File without having to read or understand the code that implements them.

Reading, writing, understanding, and analyzing specifications will be one of our first major undertakings in CS71, starting in a few classes.