Project Setup
We’re going to set up a project in CSharp first. You will need to do this for any session where we are not cloning a repository. You can refer back to this week if you need a refresher.
Opening the Editor
The editor for code (IDE) we are using is called visual studio. There is a related but completely seperate program called visual studio code. Do not confuse these.
Tip
|
Visual studio only exists for Windows. Use Rider on non-windows environments. The button locations will be different but the basic ideas will be the same. |
First Run
When you first open visual studio, you will be prompted to sign in. You can click `skip this for now' to save time.
You will be prompted for a bunch of personal preference options like colour scheme and the like. I don’t care what options you choose here. Use whatever makes the most sense to you.
Wizard
Creating a project from scratch is something you won’t need to do very much on this module. But I’m going to walk you through this process just so you know how to do it and some of the options you will need to select.
In most future workshops, I’ll give you half-complete programs to experiment with. For this session, we’ll be going from first principles.
Choose Application Type
To start with (and for portability), we’re going to be starting with a Console App. There are many extremely useful console apps within our subject (including the text editor I’m writing this in, Vim).
Tip
|
Visual Studio supports many Languages. You need to ensure you select the C# version (not the C++ or
Visual Basic) versions. I’m also using .NET core for this workshop (the cross platform one, as shown by the
Linux and macOS tags). It shouldn’t matter too much if you pick the .NET framework version.
|
Configure your Application
Name your new project something that it will be easy to identify later. In this case, i’m calling my project
COMP101_HelloWorld
. By default, it will save to your user directory. On workshop machines, it’ll be easier
to save it somewher under the D:
drive.
Tip
|
Store work on the 'D:' (scratch) drive rather than C. It’s bigger and easier to find. |
Warning
|
anything you store on this machines will be wiped. You should back up data to one drive or git (once we’ve shown you how to do that). |
Select .NET version
You next need to select the .NET version you are going to use.
-
Ensure that
.NET 6.0 (Long Term Support)
is selected. This should not have much of an impact, but I’ve only tested these instructions under this version. -
Please tick the box lablled, "Do not use top-level statements", as this will hide most of the complexity I’m about to show you. If you do, you can just copy and base from the code below.
Hello, World!
Modify the example code provided to be the following, We’ll walk through what this code is doing together.
namespace COMP101_HelloWorld
{
internal class Program
{
static void Main(string[] args){
int myValue = 0; //(1)
myValue += 42; //(2)
Console.WriteLine("Hello, World! {0:D}", myValue); //(3)
}
}
}
-
Declares a variable named
myValue
, and assigns the initial value to be0
-
Assigns a value of
42
to the (existing) variable namedmyValue
-
Calls/Invokes a method named
WriteLine
with two arguments, astring
and a variable
Method Structure
Lines (1), (2), and (3) are inside a set of curley brackets ({
and }
) attached to the Main
method. This
establishes a scope. All variables only live inside a scope and only exist inside it (in other words,
if you try to use myValue outside
of the Main
method it won’t exist).
This idea of scoping can feel alien at first, but we’re actaully fairly used to it in every-day life, we just don’t think about it too much most of the time (or most people don’t think about it, I’m told…).
In C# (and many other C-Derived languages), the Main
method is special, it is what gets run when the
program executes. This means the code inside this set of curley brackets is what gets run when the program
executes. As in (most) other languages, the program starts at start (opening {
) of the method and executes
the instructions until it reaches the end (closing }
).
For now, we’ll write most of our code in the Main
method. Later in this module we’ll be writing our own
methods. If you have programmed in Unity
, you have probably already encounted this concept
(Awake
vs Update
vs Fixed Update
).
In languages like python, newlines (and spaces!) are used to seperate statements. In C# whitespace, and newlines are not important. As
a result, you need to put semicolons ;
between statements. This also means that you need to be careful
when indenting, because this doens’t matter either. All that matters for determining scope is how many
opening {
and closing }
there are.
Note
|
style guides
Programmers like to keep their code organised. Often, we agree on how code should look for a given project. This is called a style guide or coding conventions, and there is an 'approved' one for C#. You may notice in my code snippets I sometimes won’t follow these. There are a few reasons for this: Following a style guide makes your code easier to read and for others to reason about. Visual Studio really wants you to use their style guide (and the editor will rewrite code so it follows it). Ideally, you should also follow the common conventions for that language, to make it easier for other programmers in that language to follow. As a result, I recommend you follow their conventions. You could even treat converting my code to match it to be an exercise in itself! |
Code Structure
If you are used to programming in languages like Python, this may seem quite verbose (wordy) to you. This added complexity does serve a purpose but we’ll worry about that (much) later in this module. Here are some key take-aways for the structure of this code:
-
C# code exists inside methods, that’s what the
static void Main(string[] args)
is about -
C# is object-oriented: Code lives inside methods; Methods live inside classes
-
Classes can live inside namespaces which group classes together
From these points you might get the impression that a lot of what we’re doing with this structure is grouping things together. You’d be correct. A lot of object oriented programming is about how we group things together.
Tip
|
Oh, while we’re talking about grouping things, a pet peve of mine. Code in a programming context is a mass noun. I wrote code. I don’t write codes (actually, considering I also help draft codes of conduct, in that sense I do). Code is a collection of instructions or statements. |
Variable Declaration
C# is a statically typed language. That means variables have a type associated with them. When we declare a variable, we are telling the compiler (thing that converts programming code into runnable code) what this type is.
C#, like most languages in the 'c' family of programming languages, does this by writing the type before the name of the variable. We can also choose to provide an initial value to variable, which in this case I’ve done. I’ve set this initial value to 0.
The type I have declared myValue
to be is an int
. This means it stores a whole number. We will look at
types in a future theory session.
Variable assignment
Note that on (2), we are assigning (updating) the value of the variable we declared in (1). We are are not making a new variable, we don’t need to tell the compiler the type (we already did that when we declared it).
The variable keeps its declared type for its lifetime, but we can very the value by assigning a new value.
The old value is forgotten, as a result, it is sometimes called destructive assignment. Assignment in
strongly typed (languages which enforce type correctness) is usually checked by the compiler to ensure that
you are not breaking the rules (such as declaring the variable to be an int
, but then trying to assign
a string to it).
Let’s demonstrate this now.
Modify (2) to try to assign the string (a type used for storing text), "Banana"
(with the quotes) to
myValue
. The compiler should not allow this to happen.
How does it communicate the fact this is not allowed to you? Change this line back to what it was.
Output and Formatting
Finally, we use Console.WriteLine
to output text to standard out. The first argument to this function is a
formatting string, which allows us to display text but also to specify where the variables appear in the
outputted text. This is an example of a method call.
The method name (WriteLine
) is followed by an opening bracket ((
) which is used to group its arguments.
Arguments are things which are given to a function when it executes to alter its behaviour. We have passed
two arguments to this method:
-
A string
-
The variable we declared (and assigned) earlier.
The first argument, the string, "Hello, World! {0:D}"
is a formatting string. This allows you to describe
how the other arguments passed into the function will be displayed. This actually is its own little mini-language.
You can read {0:D}
as, argument 0 (the first argument after the formatting string) should be treated as
a decimal number. We will play with this formatting string later.
Variable Questions
Q What is the name of the variable
passed into the method call marked with a (3)?
Click to reveal the answer
myValue
There are two arguments passed into the method call. The first is a string
literal argument which contains the
text 'Hello, World! {0:D}'. The second is the variable we declared on (1). The string in this case could be
considered a 'literal' because we never assign it to a variable.
(advanced) a have a whole thing on when things can 'look' like a literal but actually aren’t in different programming languages, but that’s very outside the scope of this module. See small integer caching in python or the .text/.rodata section in ELF binaries.
Q What is the type of the variable
passed into the method call marked with a (3)?
Click to reveal the answer
int
When you declare a variable (in C#), you put the type followed by the name. We declared the variable on (1), and when we did so we gave it the type int. In statically typed languages like C#, we declare a variable’s type when creating it. The compiler remembers this value and the variable will always be this type.
(advanced) We’ll talk about when two different variables can look like they are the same, but are actually different later in this module, but this doesn’t violate this rule. We’ll also see a case when we tell the compiler, 'figure out the type for me', but even this does not violate the rule.
Running your program
Ok, that’s enough theory for now. Let’s run our program and see what we get.
Press the green play button in the toolbar. You can see this in the screenshot above. A window should appear and you should be able to the output of your program.
Q Is this output what you expected? If not, what did you expect to see?
Show output and explanation
Recall the formatting string said, 'represent the variable next in the argument list as a decimal number'.
Unsurprisingly, the number 42
when represented as a decimal (base 10) number is 42.
If you skipped the section explaining what that first argument was doing, you could (reasonably) expect the output to be:
Hello, World! {0:D} 42
. That’s not an ASCII angel emoji, that’s a format string. Go back and read the section on that.
Altering your formatting string
The formatting string I provided put the output at the end. I would like you to play with this formatting string to produce the following output (don’t change the variable yet):
There are 42 lights
Tip
|
You should not need to alter the {0:D} part, but you might need to move it.
|
Conditionals (If, Else)
Often, we want our code to run when some condition is true. If you have programmed before, you will have encountered this idea already. A condition in C# looks like this:
if ( myValue == 1 ) // (1)
{
Console.WriteLine("Hello, World"); // (2)
}
else
{
Console.WriteLine("Good Bye, World"); // (3)
}
-
is a condition, if the statement in the brackets is
true
then (2) runs, else (3) runs -
is the code that runs when the condition is true, all the code between the
{
after the condition (1), and the}
before the else will be run -
is the code that runs when the condition is false, all the code between the
{
after the else and second}
will be run
Q based on this information, what would the output be in the following cases:
-
myValue
is 0 -
myValue
is 8080 -
myValue
is 1
Show answers
-
Good Bye, World ( because
0 == 1
is false) -
Good Bye, World ( because
8080 == 1
is false) -
Hello, World ( because
1 == 1
is true )
Omitting the else
Often, we don’t want any code to run when the condition is false. If we were to write this out, we would write:
if ( myValue == 1 )
{
Console.WriteLine("Hello, World");
}
else
{
// do nothing
}
However, this means we write a 'placeholder' code which does nothing. We can simply this, we simply don’t
include the else
part of the statement. This is what it would look like in our code:
if ( myValue == 1 )
{
Console.WriteLine("Hello, World");
}
Note
|
Avoiding else
In theory, providing the run if Some (very poorly designed) scripting languages don’t have any concept of 'else' at all. Instead you need to check for the opposite of the condition if you want to write an else statement. This is extremely error-prone and relies on you not modifying the contents of the variable in the body of if statements. I recommend you avoid using such programming languages. |
Scopes
Notice the brackets on the if statement. There is an important interaction with declaring variables you need
to be aware of. It is related to the concept of scope. For our purposes, we are going to treat 'scope'
as being between a set of curley brackets. A scope begins at the starting curley bracket {
and a scope
ends }
at the closing curley bracket (there are actually more complex notions of scope in other programming
languages, but this will do us just fine for now).
Variables only exist in the scope they were declared. Their lifetime is also often bounded by their scope.
Note
|
There are ways for variables (or their contents) to escape this scope, it usually involves poorly designed programs, language hacks, or languages with a different idea of 'scope' but we’ll file that under, "definitely not first year, first session content". Its also far more common in other languages (like Python, Javascript or C++). |
In other words, the variable exists from the point you declare it to the closing bracket that contains that declaration. I know that sounds complicated, but the idea is much easier when its demonstrated.
Let me show you:
void Main(string[] args) {
int myNumber = 42; // (1)
if ( myNumber == 42 ) {
string myWord = "banana"; // (2)
// I'm just doing this to show it's allowed, it will have no effect as it's already 42
myNumber = 42;
}
Console.WriteLine("MyNumber is: {0:D}", myNumber); // (3)
}
-
is a variable declaration inside the function’s scope, it stops existing at the end bracket (
}
) of the function -
is a variable declaration inside the if statement’s scope, it stops existing at the end of the if statement.
-
is after the end bracket of the if statement, therefore myWord no longer exists, but before the closing bracket for the function, therefore myNumber still exists
Because, 'myWord' only exists in the scope of the if statement, we cannot use it outside the statement. Likewise, if we had another function, the variables declared in this function cannot be used outside it. This concept can take a little while to get your head around, but it makes reasoning about what our code is doing a lot simpler.
Note
|
Extension content
There used to be a bit about getting a deeper understanding of scopes here. I’ve moved this to Deep Dive: Scopes to avoid making this too long. |
Number Guessing
Using what we’ve learnt so far (plus one additional fact) we’re going to build a simple number-guessing game. This game goes by many names, and is sometimes used as a programming exercise. I’ll describe the game briefly.
-
The game chooses a number at random (our target number) - for our example, we’ll hard-code the number
-
Until the player guesses the correct number, keep asking the player to guess a number
-
If the number is the same as the target number, show the text, 'you win' and terminate
-
If the number is smaller than the target number, display, 'higher'
-
If the number is larger the target number, display, 'lower'
-
Programming our game
-
Remove all the contents of the 'Main' method, you could also rename 'Main' to 'OldMain' and create a new empty method called 'Main' if you wanted to keep it
-
Create a variable of type
int
to store your target number, set this to be any value you like -
Create a loop of a suitable type (while, for, foreach), inside the loop:
-
Display a message using
Console.WriteLine
to prompt the user for a number -
Create a variable of type
int
to store the users guess, set it to any value for the time being -
Use an if statement to check if that number is the same as the target number,
break;
if it is -
Use another if statement to show higher or lower to the user
-
Run your game, you will need to stop it using the Stop button in the IDE. We’ve not done something important for our game, which will cause it to run forever.
If you are new to programming, I’ve written some hints to help with this: Number Guessing (Step by Step)
Loops in C#
We’ll look more at loops next week, but if you’ve not seen them in C# this is what they look like.
This prints "hello world" forever:
bool shouldRun = true;
while( shouldRun ) {
Console.WriteLine("Hello, World");
}
This prints the numbers 0 to 9:
for (int i=0; i<10; i++){
Console.WriteLine("{0:D}", i);
}
You can solve this task without knowing either of the following, but you may find this useful to know:
-
You can stop a loop running (and continue after the closing
}
) using the instruction:break;
-
You can jump to the next time round the loop using the instruction:
continue;
User Input
In C#, we can use Console.ReadLine() to read text from the keyboard. This is similar to input()
in python 3 (or raw_input()
in python 2 if you’re old enough to remember that).
string? userGuessStr = Console.ReadLine();
We will store the result into a variable of type string?
. Why does it have a question-mark after the type? Because we could not get a value back if there is an error, therefore, this method returns an optional string
. In practice this just means it could be a string, or a special value called null
, which means there is no value.
Dealing with Null Values
We need to decide what to do if we get null back. We could:
-
Ask the user again for a number, which may not work if the error was that our input is no longer available
-
Tell the user there was an error and break out of the loop, ending the game
-
Use some default value (eg, 0)
Any of these is a valid approach (being careful to avoid infinitely asking in the first case). For the sake of simplicity, I’m going to opt for the third option. I’m also going to be fancy and provide a solution using something I hinted as a solution in the 'advanced' topic. You can also use an if statement, and I’ll provide both solutions below.
We also have an added complication in that we want the value we get back to be an 'int', but the console will give us a string. We can use int.Parse
to convert a string that represents a number (eg, the string "42" to be an integer for that value). There is a potential problem here, but I’m leaving that as an extension task. For now, assume the user will always provide a valid number.
The easiest way to do this is using an if
statement:
string? userGuessStr = Console.ReadLine();
int guess = 0;
if ( userGuessStr != null ) {
guess = int.Parse(userGuessStr);
}
Ternary Conditionals
Because this is such a common thing to do (use a default value if some condition is true), there is a 'shorthand' for writing this kind of if statement, it looks like this:
myVariable = condition ? value_if_true : value_if_false;
The if statement above can therefore be written as:
string? userGuessStr = Console.ReadLine();
int guess = userGuessStr == null ? 0 : int.Parse(userGuessStr);
This means exactly the same thing as the if statement version, it’s just shorter. There is an even shorter
version which handles just the case when checking for == null
, called Null Coalesing.
Note
|
Because we have dealt with the case when userGuessStr can be null, we don’t need it to be an int? - we know the int can never be null, it’s either 0 or whatever number the user provided.
|
Place either of these into the suitable place inside your code. Ask another student to play your game.
Extension Tasks
To make this game a bit more interesting, there are a few things you could do. Once you have get other students to play your version of the game and play other people’s versions of the game.
-
Can they break it (how does their error detection work?)
-
What changes did they come up with?
Explain your solution and help others
Remember, you will all have different levels of experience. The purpose of this exercise is to be creative, play with the ideas we’ve presented and to have fun.
Try to explain your solutions and approaches to other students and help them if they are stuck.
Possible extension tasks are divided into three categories:
If you are feeling overwhelmed
Keep track of the number of lives the player has. Each time the player guesses wrong, take away one life. If they have no lives left, then the game ends.
If you are feeling confident
-
Only allow guesses within a certain range (eg, 1 to 100)
-
Strings in C# are UTF-16 encoded, which means you can use emojis in them. Add emojis.
-
As well as methods (functions) Console contains properties (variables) - one of these allows you to Alter the colours
(Advanced) If you want a challenge
-
Deal with the case when the number provides something that is not a number,
int.TryParse
can help with this - it modifies one of its arguments, so check the documentation! -
Choose the number at random, this requires creating a Random object (we’ll talk about this next week).
-
Play multiple rounds (eg, 5) of the game, after the last round tell the player how many rounds they 'won' (this requires limiting the number of guesses per round somehow - or providing a 'give up' option)
-
Anything else you can think of
In the spirit of sharing, you can find my sample code from this lab here: game.cs.