Intro
Understanding types is one of the most important basics in C#. Every type, no matter what, is ultimately connected to a “big boss” type called System.Object. This means that, behind the scenes, all types share some common features.
For example:
// This class automatically inherits from System.Object
class Music { }
// This is the same as above, just written out
class Opera : System.Object { }
No matter what you create, it all starts with System.Object. This gives you useful methods like ToString() and GetHashCode() for every object.
Types in C#: The Basics
In C#, types are grouped into two main categories:
- Value Types
- Reference Types
There’s also a special group called primitive types, which are the most basic built-in types.
Let’s break these down!
Primitive Types
Primitive types are the simplest types built into C#. They are easy for the compiler to use and map directly to types in the .NET framework.
Examples: int, double, bool, char, byte, short, long, float, decimal
- Primitive types are a subset of value types.
- They have default values (int is 0, bool is false, etc.).
- They are fast and efficient.
int count = 5; // int is a primitive type
bool isOn = true; // bool is a primitive type
Value Types
Value types hold their actual data. They’re stored in a quick-access area called the *stack *, When you assign a value type to another, it copies the value.
Examples: All primitive types, struct, enum
- Value types always have a value, even if you don’t set one.
- They can’t be null (unless you use a nullable type).
- Assignment copies the value—not just a reference.
- All value types (structs, enums, primitive types) inherit from System.ValueType.
int number = 10; // Value type, default is 0 if not set
int copy = number; // Copies the value (10)
All value types in C# (such as structs, enums, and primitive types like int) are sealed. This means you cannot inherit from a value type, nor can you use a value type as a base for another struct or class.
So, for example, it’s not possible to define any new types using Boolean, Char, Int32, UInt64, Single, Double, Decimal, and so on as base types. A value type can implement one or more interfaces if you choose
Why Are Value Types Sealed?
- Immutability of Layout: Value types have a specific memory layout, and allowing inheritance would make their size and structure unpredictable.
- Performance: The sealed nature allows the .NET runtime to optimize how value types are stored and accessed.
- Simplicity: Inheriting value types would complicate assignment, boxing, and unboxing.
The following code will not compile:
struct BasePoint { public int X, Y; }
// Error! Cannot derive from a sealed type
struct DerivedPoint : BasePoint { public int Z; }
You also cannot use a value type as a base for a reference type:
struct BaseStruct { }
// Error! Cannot derive from a value type
class MyClass : BaseStruct { }
How to Create Your Own Value Type?
Using struct
A struct is a user-defined value type. Here’s how you might create a simple value type to represent a 2D point:
public struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}
Now you can use Point as a value type:
Point a = new Point(1, 2);
Point b = a; // This copies the value (not a reference)
b.X = 10;
// a.X is still 1, b.X is 10
Using enum
All enumerations in .NET inherit from the abstract System.Enum type, which itself is a subclass of System.ValueType. The Common Language Runtime (CLR) and programming languages provide special handling for enumerations to support their unique behavior and usage.
An enum is another kind of value type you can create:
public enum DayOfWeek
{
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
- Structs and enums are the only ways to create custom value types.
- Value types are stored on the stack (or inline in objects) and behave differently from reference types (classes).
- You cannot inherit from a struct or enum, nor can they inherit from anything except System.ValueType.
Nullable Value Types
Sometimes, you want a value type to be able to hold null (for example, when the value isn’t set yet). In C#, you can do this with a “nullable” type:
int? score = null; // 'score' can now be null
if (score == null)
{
Console.WriteLine("No score yet!");
}
Reference Types
Reference types store a reference (address) to their data. The actual data lives in the “heap,” and the variable simply points to it. When you assign a reference type to another variable, it copies the reference, not the actual data.
Examples: class, string, array, delegate, interface, object
- Reference types can be null (they may not point to any object).
- If you use a reference type before setting it up, you get a NullReferenceException.
- Assignment copies the reference (address), not the data.
string name; // This is null by default
// Using 'name' before assigning a value causes an error
// Console.WriteLine(name.Length); // NullReferenceException!
How Memory Is Allocated: Value vs Reference Types
One of the most important differences between value and reference types is how and where they are stored in memory.
Value Types: Stored on the Stack
Value types are usually stored on the stack—a fast, simple part of memory. When you create a value type variable, the data lives right there in the variable.
Example:
int number = 5;
int age = 20;
Each variable holds its value directly.
Reference Types: Reference and Object Locations
Reference types are a little different. The actual object data is always stored on the heap, but where the reference itself (the variable that points to the object) is stored depends on how you declare it:
- If the variable is declared inside a method (a local variable), the reference itself lives on the stack.
- If the variable is a field of a class (or a struct), the reference lives inside that object—usually on the heap.
- If the variable is static, the reference lives in a special static memory area.
- No matter where the reference is stored, it always points to the object data on the heap.
class Pet { public string Name; }
class Person { public Pet pet; }
void Example() {
Person p = new Person(); // 'p' is a local variable (reference stored on stack)
p.pet = new Pet(); // 'pet' is a field (reference stored inside the Person object on the heap)
}
- Here, p (the reference to the Person object) lives on the stack (since it’s a local variable).
- The Person object itself is on the heap.
- Inside the Person object, the pet field (a reference) points to another object (Pet) on the heap.
What Happens When You Copy?
Value Type Copy
int a = 10;
int b = a; // b gets its own copy of the value (10)
Reference Type Copy
Person p1 = new Person();
Person p2 = p1; // p2 points to the same object as p1
Both p1 and p2 point to the same object in memory. If you change the object using either variable, both see the change!
Summary
- Primitive types are basic built-in value types, like int and bool.
- Value types hold actual values and live on the stack.
- Reference types hold references (addresses) that may be stored on the stack, heap, or static area, but the actual object data always lives on the heap.
- Assigning value types copies the data; assigning reference types copies the address.
- All types in C# ultimately come from System.Object.
Extra Tips for Beginners
- If you get a NullReferenceException, it means you tried to use a reference type that wasn’t set up yet.
- Use nullable types (int?, double?) when you need a value type that can also be null.
- Passing value types to methods copies their value; passing reference types copies the reference.
Grasping these fundamentals will enable you to write safer and higher-quality C# code. I strongly recommend reading the book CLR via C# by Jeffrey Richter to gain a deeper understanding of the language's features.





Top comments (0)