6. Mid-level Code: Objects etc

The three pillars of programming have taken us a long way, so now it is time to examine general everyday-level coding concepts that are only slightly less fundamental.

Object

C# is an OOP (Object-Oriented Programming) language in which everything is an object. The following code is illegal :

// start of program illegal.exe
int x = 1; // ILLEGAL!!!
// rest of code omitted

The ‘x’ variable here is ‘loose’. It does not belong to a object. To avoid such looseness, you must write this instead :

// start of program legal.exe
class Legal
{
     int x = 1; // LEGAL!!!
     // C-like Main() omitted
     // rest of code omitted
}

That’s better. The ‘x’ is now safely tucked away within a class.

The base on which C# is built is called object. Everything in a C# program is at root an object. An object is created with the new keyword like this :

object o1 = new Object();
object o2 = new object(); // equivalent

‘Object’ is also a type, the root type. Tautologically, in the code above ‘o2’ is an object of type ‘Object’. It is a variable. So it is also a variable of type ‘Object’. As ‘Object’ is important rather than interesting, let us quickly move on to the variables and types that programmers actually use.

Variables and Types

At the lowest level, C# is variables which come in various types. You already know that data points to a memory location in the machine. So if you read the C# code

var x = 1; 

you should know it means that ‘x’ references a memory location that contains a value of ‘1’. However, in a high-level language that is a little simplistic, as the following code illustrates :

var x = 1;
x = "foo"; // ERROR!!!!

The var keyword in C# is a sometimes-useful way of declaring a variable. It essentially means ‘any’, so ‘var x’ means that ‘x’ can be of any type at the point of declaration. Once declared, however, the type of ‘x’ is fixed. If you write ‘var x = 1’, then x is an integer. You cannot later assign a string to it. If we change var to int, the code looks a lot clearer :

int x = 1;
x = "foo"; // OBVIOUS ERROR!!!!

It is important to understand that int is an object, so we can if we want declare it like this :

int x = new int();
int y = new Int32(); // equivalent

The declaration ‘int x = 1’ is what is known as ‘syntactic sugar’, referring to when a common programming task is provided with a shortcut baked into the language itself.

We should also mention the constant. Conceptually, a constant is a variable that can’t be changed. So,

const x = 0;
var y = 0;
x = 1; // ERROR!
y = 1; // OK!

Constants are becoming increasingly fashionable in modern code-think, and with good reason. Constants don’t change, bringing a welcome level of predictability to very large programs (we might prefer to call them applications). There is growing consensus these days that if a value can be a const then it should be.

Arrays

You wouldn’t get far with just variables, which can only store a single bit of data. Hence the very venerable array. Here is array of integers :

// verbose
int[] i = new int[3];
i[0] = 1;
i[1] = 2;
i[2] = 3;

// quick'n'easy
int[] j = { 1, 2, 3 };

Given what you already know, that should be easy to follow. You know that declaring ‘i’ points to a location in memory. An array is just a group of i’s with a specified size (or ‘length‘ in C/C# terminology). So int[3] simply means a variable with a ‘length’ of three int’s. An array has an index that starts at zero, so compare :

int i = 1;
int[] a = int[1]; // length in the square brackets 
a[0] = 1; // index in the square brackets

The variable and the array say the same thing. The variable ‘i’ is set to 1, and the array index of 0 is set to 1. So the variable ‘i’ points to a value of 1, and the first and only element of a points to a value of 1.

The code below illustrates the basic usefulness of arrays :

// use a variable here
string single = "Honky Tonk Women";
// use an array here
string[] album = { "Gimme Shelter", "Love In Vain", "Country Honk",
     "Live With Me", "Let It Bleed", "Midnight Rambler",
     "You Got The Silver", "Monkey Man",
     "You Can't Always Get What You Want" }

Operators

Operators are fundamental to programming. Assigning values, arithmetic and testing for equality are all things that need to be done, and frequently, in any program.

Certain operators need no explanation :

x = y + 1; // addition operator
x = y - 1; // subtraction operator

Some are familiar in function but maybe not in appearance :

x = y / 1; // division operator
x = y * 1; // multiplication operator

The ‘=’ operator that everyone knows and loves is deceptive though in C-like languages, for it does not mean ‘equals’. The code below explains the initially-confusing family of equality operators, including those relevant to boolean logic.

// this does *not* mean 'equals' but something
// like 'assign to' or 'place the value in', so
// here 'assign the value 1 to x'
x = 1;

// the equality sign uses two equals characters '=='
if (x == y) {} // if the value of x is equal to the value of y

// an exclamation mark means 'not', so != means 'not equal to'
if (x != y) {} // if the value of x is not equal to the value of y

// bool (Boolean) values can be true or false
bool b = true;
// set c to not b: here, as b is true, not b is false
bool c = !b; // 'not' operator

if (!(x == y)) {} // same as x != y
// note: '!' is less readable here but often makes complex
// evaluations more readable

// two ampersands mean 'and'
if ((x == y) && (a != b)) {} // both tests must be true

// two bars mean 'or'
if ((x == y) || (a != b)) {} // either tests can be true

There are other operators, but that should be sufficient to illustrate what an operator is and how it is used.

Threads

Threads are among the gnarliest and most difficult aspects of programming. But this is so because of what they do, not what they are. The idea of threads (threading) is actually very simple and even coding for threads (multithreading) has been made easier and easier.

A computer program executes in a process created by the operating system. It is this process that streams to the processor all the commands and data contained in the machine code.

Some tasks take longer than others in running code. Say a particular task takes five seconds. With just the one process, the program would freeze until the task ended.

This is the purpose of threads. A thread is a sort of lightweight process — it can stream commands and data to the processor too — spawned by the main process, the process. A process can spawn multiple threads. So with a thread created, the lengthy task can be delegated to it. Now the thread takes five seconds but the main process is free and the user is happy because the program doesn’t freeze.

The code below illustrates that a simple piece of threading code is almost as easy to read as non-threaded code :

// async signals that Add is threaded
// 'asynchronous programming' is another term
// for 'threading'
/*
     ( The <int> indicates 'Task' is a 'generic' type, a
     topic discussed in the next chapter, but it is enough
     to understand that 'Task' here is tied to the integer
     type. )
*/
public async Task<int> Add(int i) 
{
    // wait for the ++i task to be completed
    return await Task.FromResult(++i);
}

. . .

// wait for a billionth of a nanosecond
int added = await Add(1);

. . .

// non-threaded code
public int Add(int i)
{
     return ++i;
}

. . .

int added = Add(1);

Threading can never be simplified however or made not gnarly. It is easier to create threads now, but they are still threads. That is, if you had five threads running, your have six (including the main process) pieces of code active. If a certain ‘i’ value is floating in and around this code, we need to ensure that its value does not get set by one thread and reset by another. This is one reason for the current enthusiasm for constants. No thread can change a value that cannot be reset. ‘Threading issues’ are the stuff of a programmer’s nightmares. (A programmer dreaming of being chased by a monster is usually far more worried about that threading bug their dream can’t solve.)

Exceptions

Look at this code (well, imagine this code) :

{
     // imagine a gigantic mass of code
}

The more gigantic in mass code gets, the more likely it will generate an error at some point. The modern way of dealing with errors is via exceptions. An exception can be thought of as an anticipated error. Exceptions work with a try-catch block.

We can rewrite the above code as follows :

{
     try
     {
          // imagine a gigantic mass of code
     }
     catch (Exception e)
     {
          // handle error
     }
}

There are many types of exception, so we can handle exceptions in a very granular way. If our program was about processing Mallumps, we could create our own custom exception :

catch (MallumpOverloadedException e)
{
     // handle error
}
catch (MallumpNotFoundException e)
{
     // handle error
}

This example also illustrates how you can finesse your exception handling with multiple ‘catch’ blocks.


Posted

in

by