To kick off our C# in Simple Terms mega-series, let's learn about the most fundamental concept C# has: the type system.

The Sample Solution

As always, there's a sample C# project over on GitHub that demos the ideas we are talking about in this project. The solution contains all parts of this series.

exceptionnotfound/CSharpInSimpleTerms
Contribute to exceptionnotfound/CSharpInSimpleTerms development by creating an account on GitHub.
Project for this post: 1TypeSystem

Strongly-Typed and Type-Safe

The first and most important thing to know about C# as a programming language is this: C# is a strongly-typed language. This means that every variable, every constant, every class, every single object ever created using C# has a type. It is impossible for an object to exist in C# without it having a type.

A young man and a young woman dance on a forest path at sunset.
Looks like they both have a type. Photo by Scott Broome / Unsplash

A type in C# (and .NET) is a set of properties about a specific kind of object. These might include the storage space it needs, the maximum or minimum size of the object, and others.

int four = 4; //Max value 2147483647 (2^31 - 1), Min value 2147483648 (-2^32) 
double twopointfive = 2.5; //Size: 8 bytes
char a = 'a'; //Size: 16 bit

The lines above demonstrate how to create a variable in C#. The first word in each line above is the type (e.g. int, double, char), the second word is the variable name (e.g. four, twopointfive, and a) and the number or character on the right side of the = is the variable's value.

Because C# is a strongly-typed language, C# is also a type-safe language. That means that objects which are instantiated as a given type (number, character, date, class, etc.) have rules in place to ensure that said instance behaves as that type. Consequently the C# compiler will allow us to add two number types together, but trying to add a number to a word will cause a error.

int five = 5;
int ten = five + five; //No problem!
int invalidTen = five + "five"; //Compilation error!
Error: Cannot implicitly convert type 'string' to 'int'

System.Object

Because no object can exist in C# without a type, there exists a "base" type that every kind of object inherits from.

In .NET, that "base" type is called System.Object.

System.Object newObject = new System.Object();

We can also instantiate this object using a simplified syntax:

object newObject = new object();

Type Inheritance

C# and .NET support the concept of type inheritance. Types can "derive" (meaning they inherit attributes, properties, and constraints) from other types. For example, all types in C# derive from the base class System.Object.

We will discuss inheritance, particularly inheritance involving classes, more thoroughly in Part 9 of this series, which covers Inheritance and Polymorphism; for now, just know that types can inherit behavior from other types.

Value Types and Reference Types

C# supports two distinct kinds of types: value types and reference types.

Value Types

Value types are objects whose value is contained by the object. In C#, value types include almost all "primitive types", which we will discuss in the next part of this series. Generally these are "simpler" types, like numbers or characters.

In C#, all value types inherit from a base class System.ValueType, which in turn inherits from System.Object, as all types must.

int myNumber; //Default value 0
bool myBool; //Default value false
double myDouble; //Default value 0

If we do not specify a value when creating a value type, they get a default value.

Reference Types

Reference types, unlike value types, are objects which exist on the memory heap. The variables we create contain a "pointer" or a "reference" to that value on the memory heap; the variable does not contain the value itself. Generally, reference types are more complex types, such as custom classes, queries, and collections (like arrays).

Array[] myArray; //Default null
MyClass myClass; //Default null
MyClass otherClass = new myClass();
If you don't know what Arrays are, they will be covered in Part 12 of this series.

A reference type, if it is initialized without a value, will have the value null. We will discuss null later in this post.

Because reference types are merely a reference (or pointer) to the object's value, it is possible for objects on the heap to no longer have any references to them. C# includes a feature called garbage collection, where automatic memory management will "reclaim" memory from reference types and other sources that are no longer being used. In most cases, you don't need to worry about the garbage collection process; it just happens behind the scenes and doesn't interfere with anything.

Implicit Types (var)

If you've read any C# code, you have probably encountered something like this:

var myValue = 5;

var is a special keyword in C#. It is a "placeholder" for a type which will be determined by the value of the variable. For example, in the line above "myValue" is initialized as type int, because the value assigned to it is a simple integer (5).

It is important to note that var is not a type unto itself. It is only a placeholder.

Using var, we can make our code easier to read. For example, imagine we have a set of variable declarations:

double myDouble = 5.6;
char myChar = 'a';
MyClass myClass = new MyClass();
int myInt = 7;

Using the var keyword, we can simplify our code a bit:

var myDouble = 5.6;
var myChar = 'a';
var myClass = new MyClass();
var myInt = 7;

In this way, each of these variables has their type implicitly assigned from their value.

Null

We must also talk about a special type in C#: null.

Null is a literal type that represents a null reference; that is, one which does not point to an object on the memory heap. It is also the default value of reference types when they are created.

//The below two lines will be treated as identical.
MyClass myClass = null;
MyClass myClass;

We use null when a reference type object does not yet have a value. Instances of reference types, by default, have the value null.

When writing code, we often have to do null checking, which is when we confirm that a particular object is or is not currently null.

if (myObject == null)
{
    //Do something
}
See Part 5 for more information on IF clauses.

However, we cannot use the var keyword and assign the variable to null; that will throw a compilation error. This is because the C# compiler cannot determine the type of a variable if the value is null.

var myClass = null;
Error: Cannot assign null to an implicitly-typed variable

Glossary

  • Strongly-typed: Every object must have a defined type.
  • Type: a set of properties about a given object.
  • Type-safe: Objects which are created as a type will behave as that type. Alternately: types cannot be combined in an unsafe manner.
  • Type Inheritance: Types can assume the properties, attributes, and constraints of another type.
  • Instance - A object created from a definition, where the definition can be a class, struct, value, etc. Instances are said to be instantiated from classes, structs, or other C# objects.
  • Instantiate - Create an instance of. For example, in the line int myYear = 2020;, myYear is an instance of type int.
  • Value type: An object which carries its value around with it.
  • Reference type: An object which is a pointer or reference to a location in the memory heap, where the value of the object exists.
  • Garbage collection: A feature of C# where unused allocations in the memory heap are automatically freed.
  • Null: A literal type in C# that represents an unknown value.
  • Null-checking: A programming process or pattern where reference types are checked for being null prior to being used.

New Keywords

  • object - A "shortcut" reference to the root type System.Object.
  • var - A placeholder for a type. The actual type is determined by the value of the instance.
  • null - A special value that represents a missing reference.

Summary

The type system in C# is robust, able to support value and reference types as well as the special type null, though sometimes we need to use null-checking before we manipulate objects that can be null.

All types support type inheritance, where types may adopt the properties and constraints of other types, and because C# is a strongly-typed language, every object ever created must have a type. We can create implicit variables using the var keyword, and every single object in C# will inherit from the base class System.Object.

Thanks for reading! In the next post, we discuss the basic types available to us in C#, as well as literal and nullable types. Check it out!

C# in Simple Terms - Primitive Types, Literals, and Nullables
int, string, char, double, decimal, float, long, short, byte, and more!

Oh, and if you've got questions about C# type's system, I'm here to help! Ask questions in the comments below!

Happy Coding!