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 10, which appeared in November 2025 and includes C# 14, the latest version of the C# language.
In this course we'll learn various features of C# found in the language through C# 13. We won't use any features of C# 14. One reason for that is that ReCodEx includes only .NET 9 and C# 13, 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:
C# is much faster. A C# program might run 10-20 times as fast as its Python equivalent.
C# is statically typed. That means that every variable (including function parameters) has a fixed type, and can only hold values of that type. This has two major benefits. One is that the compiler can perform type checking, which can find many kinds of bugs at compile time. Another benefit is that it lets the compiler generate much more efficient code, which makes programs faster as we observed just above.
On the other hand, some disadvantages of C# include the following:
C# is more verbose: often you have to type more characters than you would in Python to get the same effect.
C# has no REPL. If you want to experiment with a program, you have to modify its source code and recompile it.
The compiler is a bit slow: compiling even the simplest "hello, world" program takes a second or two.
Go to the .NET home page, then click Download. Then follow the instructions to install .NET 9. (If you want to install .NET 10 with C# 14, you can, but be warned that any features that are new in C# 14 will not work on ReCodEx, which only has C# 13.)
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 9.0.311 $
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.)
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!
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? 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.
So if we plan to submit our program to ReCodEx, we'll need to write it differently:
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.
To start writing code in C# we'll need to know about various built-in types. First, here are several predefined integral types:
int – a signed 32-bit integer (-231
to 231 – 1, i.e. −2,147,483,648 to 2,147,483,647)
uint - an unsigned 32-bit integer (0 to 232
– 1, i.e 0 to 4,294,967,295)
long – a signed 64-bit integer (- 263
to 263 – 1, i.e. 9,223,372,036,854,775,808 to
9,223,372,036,854,775,807)
ulong - an unsigned 64-bit integer (0 to 264
– 1, i.e. 0 to 18,446,744,073,709,551,615)
byte – an unsigned 8-bit integer (0 to 255)
C# includes many more integer types of various fixed sizes, but the ones listed here are enough for our purposes for the moment.
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.
Notice that an integer constant may contain embedded underscores for readability:
int i = 1_000_000_000;
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;
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
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
Because integer types in C# (unlike Python) have a fixed size, an integer calculation may overflow, i.e. go past the range of allowable values. In this case the higher-order bits of the value will be lost, which effectively computes the value mod 2b for a type with b bits. For example:
uinti=2_000_000_000;i*=3;Console.WriteLine(i);
6,000,000,000 is outside the range of a 32-bit unsigned integer, so the calculation will overflow and this program will print
1705032704
This value is 6,000,000,000 mod 232.
Overflow for a signed integer type may even produce a negative value:
int i = 1_000_000_000; WriteLine(i * 3); // writes -1294967296
Here are two built-in types that represent floating-point numbers:
float
– 32 bits, 7 significant digits (approximate range: 10-45
to 1038)
double
– 64 bits, 15-16 significant digits (approximate range:
10-324 to 10308)
The type of a floating-point constant (e.g. 3.4) is
double
by default, so this will fail:
float f = 3.4; // error: can't implicitly convert double to float
To create a constant of type float, append the character
'f':
float f = 3.4f; // OK
I generally recommend using double instead of float,
since 64-bit floating-point operations are inexpensive on modern
CPUs. (On the other hand, if you have a large array of floating-point
values, you can save memory by using float as the
element type if you don't care about the extra precision of a
double.)
Be careful with division. The following code will print 0, not 0.6:
int i = 3, j = 5; double d = i / j; // integer division! Console.WriteLine(d); // prints 0
That's because the division operator / will perfom an
integer division when given two integer values. To perform a
floating-point division, use a type cast:
int i = 3, j = 5; double d = (double) i / j; // floating-point division Console.WriteLine(d); // prints 0.6
Alternatively, you may multiply by 1.0 to convert an integer to a double:
double d = 1.0 * i / j; // floating-point division
Note that a float or double may hold the
special values NegativeInfinity or PositiveInfinity,
which are sometimes useful. For example:
double d = double.PositiveInfinity;
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:
int i; ... long l = i; // implicit conversion
This sort of conversion is called a widening conversion because, for example, a long has a wider range than an int.
You can use an explicit conversion to force a conversion between any two numeric types. This is accomplished with a type cast:
ulong l = 5_000_000_000; uint i = (uint) l;
If the destination type cannot hold the source value, it will wrap around or be truncated. In the example above c will be 705,032,704, which is 5,000,000,000 mod 232.
The conversion from ulong to uint is a narrowing conversion because uint has a narrower range than ulong. C# requires a type cast for any narrowing conversion.
The bool
type represents a Boolean value, namely either true
or false.
(These constants are not capitalized as they were in Python.)
boolb=true;boolc=false;
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).
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.
C# includes the boolean operators ! (not), ||
(or), && (and). For example:
if (!(0 <= x && x < 10))
WriteLine("out of range");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.
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 initializer (e.g. int
i = 0)
executes once at the beginning of the loop. It contains either a
variable declaration, or one or more statements separated by commas.
The condition (e.g. i <
10) is
evaluated before every loop iteration. If it is false, the loop
terminates.
The iterator (e.g. i += 1)
executes at the end of every loop iteration. It contains one or more
statements, separated by commas.
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");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