Programming 2, summer 2024
Week 1: Notes

introduction to C#

C# is a popular programming language that first appeared in 2000. It is mostly an object-oriented language, but also includes some functional features such as lambdas. Originally C# was very similar to Java, but it has evolved in its own way over the past 20+ years.

C# is developed by Microsoft as part of an open-source software package called .NET, which includes the C# compiler and runtime as well as a large collection of built-in libraries. The latest version is .NET 8, which appeared in November 2023 and includes C# 12, the latest version of the C# language.

In this course we'll learn various features of C# found in the language through C# 11. We won't use any features of C# 12. One reason for that is that ReCodEx includes only .NET 7 and C# 11, so if your program uses newer features then it will not work on ReCodEx.

In Programming 1 we learned Python. Some advantages of C# over Python include the following:

On the other hand, some disadvantages of C# include the following:

installing C#

Go to the .NET home page, then click Download. Then follow the instructions to install .NET 7. (If you want to install .NET 8 with C# 12, you can, but be warned that any features that are new in C# 12 will not work on ReCodEx, which only has C# 11.)

If you are on a Mac with an ARM processor (e.g. an Apple M1 or M2), be sure to download and install the Arm64 version of the installer, not the x64 version. The Arm64 version will be more efficient on your machine, and the x64 version may not show up in your path after installation. The x64 installer might be selected by default on the download page, in which case you'll need to click the small triangle and select the Arm64 version instead.

After you've installed .NET, the dotnet program should be available in your path so that you can run it from the terminal:

$ dotnet --version
7.0.116
$

creating a project

To create a new C# project, first create an empty directory. It can have any name you like – let's suppose it's called "hello". Then open a terminal window in that directory, and run

$ dotnet new console

That will create a couple of files including Program.cs and hello.csproj.

Now open the directory in an IDE. I recommend using Visual Studio Code. You may see a message "The 'C#' extension is recommended for this file type". If so, click the Install button under the message. Visual Studio Code will install the extension that provides C# support. Furthermore, you will see a message "Required assets to build and debug are missing. Add them?". Click the Yes button under the message.

(As an alternative to the above, you can perform the steps in a different order. Create an empty directory, and open it in Visual Studio Code. Then press Ctrl + ` to open a terminal window inside Visual Studio Code, and run "dotnet new console" inside that terminal. The result will be the same.)

hello, world

Let's look at the file Program.cs created by "dotnet new console":

Console.WriteLine("Hello, World!");

The program calls WriteLine(), which is a static method of the Console class. Notice that the string "Hello, World!" is enclosed in double quotes. You must use these for multi-character strings in C#. Also notice that this statement, like every statement in C#, ends with a semicolon.

Let's run the program. Open a terminal window in the project directory (in Visual Studio you can do so by typing Ctrl+Shift+C). Then type

$ dotnet run

That will compile the program and run it, producing the expected output:

Hello, World!

hello, world for ReCodEx

The one-line program in the previous section seems simple enough. Alas, if you submit it to ReCodex then it will fail to compile, with error messages such as these:

error CS8804: Cannot specify /main if there is a compilation unit with top-level statements.
Program.cs(1,1): error CS0103: The name 'Console' does not exist in the current context

What is wrong? Actually there are two problems.

The first is that this program contains top-level code, i.e. code that is not inside a method in a class. This is a relatively new feature in C# (it first appeared in C# 9 in 2020), and for technical reasons is not supported on ReCodEx.

The second problem is that this program uses an implicit namespace, namely the System namespace that includes the Console class. Usually in C# when you want to use a class in any namespace, you must import that namespace via a "using" statement. However, certain namespaces including System are automatically (i.e. implicitly) available – you don't have to import them. And so we were able to call Console.WriteLine() without importing System. This is another relatively new feature in C#, and for technical reasons is not supported on ReCodEx.

So if we plan to submit our program to ReCodEx, we'll need to write it differently:

using System;

class Hello {
    static void Main() {
        Console.WriteLine("Hello, World!");
    }
}

The name "Main" is special: it denotes the top-level entry point to a C# program. There can be only one class with a Main method in your entire program.

In the program above, Visual Studio Code will display the line "using System;" in gray. If you hover over it, you'll see a message "Unnecessary using directive". That's because on the local machine the System namespace is automatically available.

If you're writing a program that you intend to submit to ReCodEx, I recommend that you disable implicit namespaces in your project's .csproj file:

<ImplicitUsings>disable</ImplicitUsings>

With that change, the line "using System;" will be required, so Visual Studio Code won't gray it out. More generally, you will need to write a using statement for any namespace that you use, which is consistent with ReCodEx's expectations.

integral types

To start writing code in C# we'll need to know about various built-in types. First, here are several predefined integral types:

C# includes many more integer types of various fixed sizes, but the ones listed here are enough for our purposes for the moment.

declarations and assignments

A variable declaration declares one or more variables. Each variable must have a type:

int i;
long j, k;

An assignment statement assigns a value to a variable:

i = 14;
j = 1000;

It's possible to combine a declaration and assignment into a single statement:

int x = 10;
int y = 20, z = 30;
x = 40;

As you might expect, the line "x = 40" modifies x, changing it from 10 to 40.

integer constants

Notice that an integer constant may contain embedded underscores for readability:

int i = 1_000_000_000;

comments and whitespace

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

int x = 1000;    // one thousand

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

/*
  this is a comment with
  multiple lines
*/

Note that in C# (unlike Python) basically ignores whitespace. To be more precise, all sequences of whitespace (including newlines) are equivalent to a single space. So you can arrange characters onto lines however you like. For example, the following line is valid, though it is not great style:

int x = 10; int y = 20; x = 30; y = 40;

arithmetic operators

C# includes the arithmetic operators + (addition), - (subtraction), * (multiplication), / (division) and % (remainder). There is no operator specifically for integer division (like // in Python). Instead, the / operator performs integer division if both its arguments have integer types:

Console.WriteLine(7 / 3);    // will write 2

(In the next lecture, we'll learn about floating-point types and will see that this operator can also perform floating-point division.)

The result of integer division is truncated toward zero. For example, -17 / 5 is -3. (This differs from some other languages such as Python, which truncates toward -∞. In Python, -17 // 5 is -4.)

The result of the % operator is consistent with integer division. For example, -17 % 5 is -2, because

-17 = 5 * (-3) + (-2)

(This is also different from Python, in which, x % 5 will always be in the range from 0 .. 4.)

C# also includes the compound assignment operators +=, -=, *=, /=, and %=. They work just like in Python:

int x = 10;
x += 5;      // now x is 15
x *= 2;      // now x is 30

bool

The bool type represents a Boolean value, namely either true or false. (These constants are not capitalized as they were in Python.)

bool b = true;

bool c = false;

relational operators

C# includes the relational operators == (equals), != (does not equal), < (less than), <= (less than or equal to), > (greater than), >= (greater than or equal to). All of these names are just like in Python. Each of these operators produces a value of type bool.

The equality operators == and != will work with any of the types we've seen so far. The other comparison operators work with integral types and characters (but not bools or strings).

if

An if statement executes a statement if a condition is true. If there is an else clause, it is executed if the condition is false. For example:

if (i > 0)
    Console.WriteLine("positive");
else
    Console.WriteLine("negative");

Notice that the condition in an if statement must always be surrounded by parentheses.

Either the 'if' or 'else' clauses may contain multiple statements, surrounded by braces. For example:

if (i > 0) {
    Console.WriteLine("positive");
    Console.WriteLine("greater than zero");
} else {
    Console.WriteLine("negative");
    Console.WriteLine("not greater than zero");
}

If you wish, you may even surround a single statement by braces. For example, the first if statement above could be written

if (i > 0) {
    Console.WriteLine("positive");
} else {
    Console.WriteLine("negative");
}

Whether to use braces in this situation is a question of style.

boolean operators

C# includes the boolean operators ! (not), || (or), && (and). For example:

if (!(0 <= x && x < 10))
    WriteLine("out of range");

while

A while loop iterates as long as a condition is true. Its body may contain either a single statement, or (more typically) a group of statements surrounded by braces:

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

Notice that the condition in an while statement must always be surrounded by parentheses.

for

A for loop in C# looks like this:

for (int i = 0 ; i < 10 ; i += 1)
    Console.WriteLine(i);

To be more precise, a for statement contains three clauses, separated by semicolons, plus a loop body.

The code

for (initializer; condition; iterator)
    body;

is precisely equivalent to

initializer;
while (condition) {
    body;
    iterator;
}

Note that the intializer, condition, and/or iterator may be empty. An empty condition is equivalent to true. They may even all be absent:

for (;;)
    Console.WriteLine("hi");

This is the same as

while (true)
    Console.WriteLine("hi");

break and continue

C# includes the break and continue statements. You can use them in any loop, and they work just like in Python:

for (int i = 0 ; i < 5 ; ++i) {
    if (i == 1)
        continue;  // skip this iteration
    if (i == 3)
        break;     // exit the loop
    Console.WriteLine(i);
}

The code above will print

0
2

char

The type char represents a 16-bit Unicode character. A character constant is enclosed in single quotes, e.g. 'x' or 'ř'.

There are 216 = 65,536 possible 16-bit values. Unfortunately, this is not enough to represent every possible Unicode character, since Unicode code points may be as large as 10FFFF16 = 1,114,11110. So not every Unicode character will fit in a C# char. This is somewhat unfortunate, and is partially due to the fact that Unicode had a smaller range of characters when C# was designed over 20 years ago.

Fortunately, most common characters in living world languages have Unicode values that will fit in 16 bits. However, emoji and other graphical characters may not. For example, the tomato character (🍅) has a Unicode value of 1F34516, which is larger than the largest possible 16-bit value FFFF16. So it won't fit in a C# char:

char c = '🍅';  // error: too many characters in literal

However it can be stored in a C# string:

string t = "🍅";

This is a string of length 2, since it must be stored as two chars:

Console.WriteLine(t.Length);  // writes 2

By contrast, Python has 32-bit characters, so this string would have length 1 in Python (which arguably makes more sense).

string

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

We can access characters in a string using the [] operator:

string w = "watermelon";
char c = w[0];  // now c = 'w'
char d = w[2];  // now c = 't'

An index preceded by ^ counts from the end of the string (like a negative index in Python):

char e = w[^1];  // now e = 'n'

It's possible to extract a range of characters from a string:

string w = "watermelon";
string x = w[2..5];  // now x = "ter"
string y = w[5..];   // now y = "melon"

Notice that the range above includes all characters from the character w[2] up to but not including the character w[5]. (It's just like "w[2:5]" in Python.)