Types

Types

A type is a template for data to be stored. We have encountered integers (int) in our previous examples. We have both seen these as literals, raw representations such as 10, 35, 28458 etc as well as seeing values assigned to variables of type int.

int newInteger = 42;

A variable represents a storage location that can contain different values over time. A storage location that cannot change its value is referred to as a constant and requires the const word when defining a type.

const int constantInteger = 1;

Predefined Types

There are a number of predefined types that are already recognised by the compiler, integer being just one of them.

The full list of predefined types that C# supports can be found below

Keyword Aliased Type Description Range
bool Boolean Logical Boolean true or false
byte Byte Unsigned 8-bit integer 0 to 255
char Char A single 16-bit Unicode character U+0000 to U+FFFF
decimal Decimal A 128-bit data type with 28–29 significant digits (–7.9 × 1028 to 7.9 × 1028) / (100 to 28)
double Double Double-precision 64-bit floating point up to 15–16 digits ±5.0 × 10–324 to ±1.7 × 10308
float Single Single-precision 32-bit floating point up to 7 digits ±1.5 × 10–45 to ±3.4 × 1038
int Int32 Signed 32-bit integer –231 to 231 – 1
long Int64 Signed 64-bit integer –263 to 263 – 1
sbyte SByte Signed 8-bit integer –128 to 127
short Int16 Signed 16-bit integer –32,768 to 32,767
uint UInt32 Unsigned 32-bit integer 0 to 4,294,967,295
ulong UInt64 Unsigned 64-bit integer 0 to 18,446,744,073,709,551,615
ushort UInt16 Unsigned 16-bit integer 0 to 65,535
object Object Base type of all other value and reference types, except interfaces N/A
string String A sequence of Unicode characters N/A

Custom Types

We can use any number of predefined types to create complex, custom types. One way to do this is to define the structure of a class.

public class Car
{
    int horsePower;
    string modelName;

    public Car(int horsePower, string modelName) //Constructor
    {
        this.horsePower = horsePower;
        this.modelName = modelName;
    }

    public void PressHorn()
    {
        Console.WriteLine("Beep beep!");
    }
}

Classes have members which come in the form of data members or function members.

In the case of the car, the data members are horsePower and modelName which are an integer and string respectively. Remember a string is just a collection of characters.

The function members of the class are the Car() and PressHorn() functions.

Predefined and Custom Type Similarities

Custom and predefined types are similar in the respect that they both have data and function members. For example, the string type has many useful functions relating to the processing of strings such as a Replace function which will replace a substring found in a string.

string badJoke = "What’s the best thing about Switzerland? I don’t know, but the flag is a big plus.";

Console.WriteLine(badJoke.Replace("plus","minus"));

//outputs: What’s the best thing about Switzerland? I don’t know, but the flag is a big minus.

Constructors

A constructor is a special type of function that can be found within a class definition. The constructor is run every time a new instance of that class is created, or every time that blueprint is used to create a real object. For example, the Car class has a constructor function that takes an initial value for the horsePower and modelName for a car.

Constructors will always have the same name as the class they are contained within, this is why the constructor is called Car.

Car newCar = new Car(400, "Maston Artin");
Console.WriteLine(newCar.horsePower) //Outputs 400
Console.WriteLine(newCar.modelName) //Outputs Maston Artin

Instance and Static Members

We have explored data members and function members. If the data and functions act on an instance of the target type, they are referred to as instance data and instance functions.

If the data and function members do not require an instance of the type they are referred to as static. A popular example of a static function is the WriteLine method that belongs to the Console class. The Console class itself is actually static which means you cannot create an instance of Console.

Lets demonstrate the instance and static members in a code example

public class Car
{
    public int horsePower;              //Instance member
    public string modelName;            //Instance member
    public int static numberSold;       //Static field

    public Car(int horsePower, string modelName) //Constructor
    {
        this.horsePower = horsePower;
        this.modelName = modelName;
        numberSold++;                    //Increment the static sold field
    }

    public void PressHorn()
    {
        Console.WriteLine("Beep beep!");
    }
}

using System;

class Program
{
    static void Main()
    {
         Car car1 = new Car(150, "Piat Fanda");
         Car car2 = new Car(22, "Non Reliant Robin");

         Console.WriteLine(car1.horsePower.ToString())  //Outputs 150
         Console.WriteLine(car2.horsePower.ToString())  //Outputs 22

         Console.WriteLine(Car.numberSold.ToString())  //Outputs 2
         Console.WriteLine(car1.numberSold.ToString()) //This will Error
    }
}

Access Modifiers

Access modifiers determine the visibility of members to other classes. There are 6 main access modifiers for classes in C#, we will learn about 3, if you wish to learn about all 6, please visit https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers

Public Access Modifier

If a member has the “public” access modifier before it then this member will be visible to other classes. Marking a member as Public allows for classes to communicate and represents the elements of your code that you wish to expose.

Private Access Modifier

If a member has the “public” access modifier before it then this member will not be visible to other classes. Marking a member as private allows you to hide the inner workings of your class without the risk of external interaction.

Private members can only be accessed by the same class, for example, if you had a function devalueCarPrice(int depreciation) within the Car class, this function would be able to access the private member price.

Protected Access Modifier

If a member has the “public” access modifier before it then this member will only be visible to the same class or classes that are derived from that class.

Type Conversion

It is possible to convert between compatible types. By converting a new value will be created from the existing one.

Implicit Conversion

Implicit conversions can happen automatically as the compiler can guarantee that they will succeed and there is no information loss in the conversion. An example of implicit conversion is converting an integer type to a long type.

int toCovert = 42;
long converted = toConvert; //Implicit conversion as the long type can hold 42

Explicit Conversion

An explicit conversion requires a process called casting. Casting indicates the type we wish to convert to. Explicit conversions cannot be guaranteed to work nor do they ensure that there will not be information loss. For this reason, implicit conversions are generally a lot safer to perform.

Reference and Value Types

C# has four categories of types with the underlying difference of each being how they are treated in memory. We will cover the main two: Value types and Reference types. More information about generic type parameter and pointer types can be found at https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/

Value Types

A variable that is a value type is just a value such as an integer. A standard C# integer is represented as 32 bits of data and is a value type.

An alternative to a class is called a struct and still allows for complex, custom objects. Unlike a class, a struct is a value type.

public struct Car
{
    public int noOfWheels;
    public string make;
}

Value types are stored in memory and identified by their identifier.

Variable to memory location

When a value type variable is assigned to another variable a new copy of the value will be created. The existence of the two variables is not linked in any way. When changes occur to one variable they do not impact the other.

public struct Car
{
    public int noOfWheels;
    public string make;
}

Car car1 = new Car();
car1.noOfWheels = 4;

Car car2 = car1;
Console.WriteLine(car1.noOfWheels); //Outputs 4
Console.WriteLine(car2.noOfWheels); //Outputs 4

car2.noOfWheels  = 3;
Console.WriteLine(car1.noOfWheels); //Outputs 4
Console.WriteLine(car2.noOfWheels); //Outputs 3

Reference Types

Rather than pointing directly to a value, a reference type instead stores a reference to an object that stores the value rather than just the value. This means that if you copy a reference type, you are copying the reference and not the value, attempting to change a member variable of the copied variable will result in both.

Reference Types

For example, using the Car class

public class Car
{
    public int horsePower;              //Instance member
    public string modelName;            //Instance member

    public Car(int horsePower, string modelName) //Constructor
    {
        this.horsePower = horsePower;
        this.modelName = modelName;
    }
}

class Program
{
    static void Main()
    {
         Car car1 = new Car(150, "Piat Fanda");
         Car car2 = car1;

         Console.WriteLine(car1.horsePower); //Outputs 150
         Console.WriteLine(car2.horsePower); //Outputs 150

         car2.horsePower = 100;

         Console.WriteLine(car1.horsePower); //Outputs 100
         Console.WriteLine(car2.horsePower); //Outputs 100

    }
}
Car class and object meta data

Null Types

A reference can have the literal null assigned to it, this means that the object does not point to a position in memory and is instead unassigned.

Trying to access a data member of an object that has been assigned null will generate a runtime error.

Value types cannot have null assigned to them unless they are a nullable type – we will visit these later.