Understanding Code

Article Info

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.

Finding Visual Studio

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.

Skipping signing into visual studio

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

Hello World output

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

Hello World output

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

Hello World output

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.

Program.cs
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)
        }
    }
}
  1. Declares a variable named myValue, and assigns the initial value to be 0

  2. Assigns a value of 42 to the (existing) variable named myValue

  3. Calls/Invokes a method named WriteLine with two arguments, a string 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:

  1. My primary programming languages (C++, Python, historically Java) do not follow this convention

  2. The groups and programmers I’ve histoically coded with have their own programming style guides, which I follow by muscle memory.

  3. I want a code example to fit on a slide

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.

Example 1. Variable Types

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.

The run button

The Visual Studio Run Button

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
Console output

Hello World output

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)
}
  1. is a condition, if the statement in the brackets is true then (2) runs, else (3) runs

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

  3. 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 true section of the code doesn’t alter myValue, you could replace the else statement with another if statement that checks if the condition is false (in my example, myValue != 1).

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)
}
  1. is a variable declaration inside the function’s scope, it stops existing at the end bracket (}) of the function

  2. is a variable declaration inside the if statement’s scope, it stops existing at the end of the if statement.

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

  1. The game chooses a number at random (our target number) - for our example, we’ll hard-code the number

  2. Until the player guesses the correct number, keep asking the player to guess a number

    1. If the number is the same as the target number, show the text, 'you win' and terminate

    2. If the number is smaller than the target number, display, 'higher'

    3. If the number is larger the target number, display, 'lower'

Programming our game

Example 2. Creating 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:

Dealing with null 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:

Dealing with null, using a ternary
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

  1. Only allow guesses within a certain range (eg, 1 to 100)

  2. Strings in C# are UTF-16 encoded, which means you can use emojis in them. Add emojis.

  3. As well as methods (functions) Console contains properties (variables) - one of these allows you to Alter the colours

(Advanced) If you want a challenge

  1. 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!

  2. Choose the number at random, this requires creating a Random object (we’ll talk about this next week).

  3. 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)

  4. Anything else you can think of

In the spirit of sharing, you can find my sample code from this lab here: game.cs.

Graduation Cap Book Open book GitHub Info chevron-right Sticky Note chevron-left Puzzle Piece Square Lightbulb Video Exclamation Triangle Globe