The Type System
How C# uses types to catch bugs at compile time, and the crucial difference between value types and reference types
We've been using the word type for a while now. This page is the one where we slow down and look at it properly. C# is what's called a statically typed language, and the type system is one of the biggest reasons C# programs are easier to maintain than programs in dynamically typed languages.
Static typing in one sentence
In C#, every variable, parameter, field, and return value has a type that is known at compile time, and the compiler refuses to compile any code that uses those values in a way that doesn't match.
That's it. The compiler is doing a constant check: does this code make sense given the types?
Static typing is not glamorous. But the moment you have a program of more than a few hundred lines, it becomes the difference between "works the first time" and "fails in production at 3 AM."
What a type really is
A type is a promise about what a value can do. When you say
int age = 30;, you're promising:
- This box holds a 32-bit integer.
- You can add it, compare it, increment it.
- You cannot call
.Substring(0, 3)on it (that's a string operation).
The compiler reads those promises and enforces them. If you try to
call .Substring on age, you don't find out at 3 AM — you find
out when you press Build.
Built-in primitive types (reminder)
We saw most of these last chapter. A summary, plus some new ones:
| Category | Types | Example |
|---|---|---|
| Whole numbers | byte, short, int, long | 42 |
| Unsigned whole numbers | byte, ushort, uint, ulong | 42u |
| Floating-point | float, double | 3.14 |
| High-precision decimal | decimal | 19.99m |
| Truth values | bool | true |
| One character | char | 'A' |
| Text | string | "hello" |
| "Any object" | object | (anything) |
Type conversions
Sometimes you really do need to turn one type into another. C# has two flavors of conversion.
Implicit conversions (safe, automatic)
If the conversion can't lose information, the compiler does it for you with no syntax:
Explicit conversions (you take responsibility)
If the conversion might lose information, the compiler refuses
unless you cast explicitly with (type)value:
The (int) is the compiler saying "OK, you're on the hook for any
data you lose."
Parsing strings
A very common operation: turn a string like "42" into an int.
int.Parse throws an exception on bad input. int.TryParse
returns false and gives you a default value instead — that's
usually the safer choice when input might be junk.
Value types vs reference types
This is the single most important distinction in the C# type system. Knowing it cleanly will save you from many head-scratching bugs.
The difference is in what gets copied when you assign or pass to a method.
Value types: assignment copies the value
a is still 5. Changing b did nothing to a. They are
independent boxes that happened to hold the same number for a
moment.
Reference types: assignment copies the reference
A reference type lives on the heap. The variable holds a reference (an address) to the object, not the object itself.
a and b are two names for the same list. Adding through
b shows up through a, because there is only one list on the
heap.
If you wanted a real copy, you'd ask explicitly:
Why strings act "like values"
Strings are reference types — but they are also immutable. You cannot change the contents of a string once it's created. Every "change" creates a new string.
Because strings can't be mutated, you get "value-like" behavior
even though the underlying type is a reference type. This is why
most programmers think of string as "basically a primitive."
Methods, references, and surprises
Reference behavior also matters when you pass objects to methods.
AppendOne reaches through the reference and changes the list.
Reassign only changes its local copy of the reference, so the
caller doesn't see it. This trips up beginners constantly. Take a
minute to picture it:
nullable types
A reference variable can hold a special value called null, which
means "no object at all."
In modern C#, string (without ?) means "definitely not null,"
and string? means "might be null." The compiler warns you if you
forget to check.
We will come back to nulls in Error handling. For now, just know
they exist, and that the ? is the language's way of saying "be
careful."
Test your understanding
Static typing means:
The values of variables can't change
The runtime checks types as the program runs
The compiler knows the type of every variable and refuses to compile code that uses values in a way that doesn't match their type
C# uses no types at all
After this code runs:
var a = new List<int> { 1, 2 };
var b = a;
b.Add(3);
What is in a?
Hint: List<T> is a reference type — think about whether b = a copies the list or just another name for it.
[1, 2]
[3]
[1, 2, 3]
Compile error
What's the safest way to convert a user-supplied string to an integer?
(int)userInput
int.Parse(userInput) and ignore the result
int.TryParse(userInput, out int n) and handle the false case
Convert it to double first and then to int