← Open in the full interactive course (progress tracking, search & more)

Understanding value types and reference types is the foundation of how memory works in .NET.

Imagine you're building a banking application. You create a Customer object with a name, account number, and balance. When you pass that object to a method, does the method get a copy of the customer or a reference to the original? If it gets a reference, any changes inside the method affect the original object. If it gets a copy, changes are isolated.

This is the fundamental difference between value types and reference types — and it affects everything from performance to bug prevention. In this lesson, you'll learn exactly what they are, how they work in memory, and how to choose the right one for your code.

What Is It?

The Simple Explanation

Every piece of data in your C# program lives somewhere in memory. Value types hold their data directly where they are declared. Reference types hold a pointer (a reference) to where the data actually lives.

The Technical Definition

In C#, types are divided into two fundamental categories:

Category Storage Assignment Behavior Examples
Value Type Usually on the stack (or inline in a reference type) Copy — the entire value is duplicated int, bool, DateTime, struct
Reference Type On the heap — variable holds a reference (pointer) Reference — the reference is copied, data is shared string, class, array, object, record class

Important: string is a reference type, but it behaves like a value type in many ways because it is immutable — you can't change the contents of an existing string, so modifications create new strings. We covered this in the previous lesson.

Why Does It Exist?

The Problem

Memory is not infinite, and how you use it affects performance. If every piece of data were stored the same way, you'd either:

Real programs need both:

The Solution

.NET gives you both value types and reference types. You choose which behavior you need for each piece of data. Value types are cheap and isolated; reference types are efficient for sharing and large data.

This design also enables:

Big Picture

Here's how value types and reference types fit into the memory model of a .NET application:

MEMORY LAYOUT
Stack
Value types live here directly: int, bool, struct fields, and references (pointers) to heap objects.
Heap
Reference types live here: class instances, string objects, array data, object instances. The stack holds references to these objects.
Garbage Collector
Automatically reclaims memory from heap objects that are no longer referenced. Value types are cleaned up when they go out of scope — no GC overhead.

How It Works

Step 1 — Declaring a value type

int number = 42;

The runtime allocates exactly 4 bytes on the stack. The variable number contains the value 42 directly.

Step 2 — Declaring a reference type

Customer customer = new Customer();

The runtime allocates memory on the heap for the Customer object. The variable customer on the stack holds a reference (a memory address) pointing to that heap object.

Step 3 — Assigning copies values

int a = 10;
int b = a;   // b gets a COPY of a's value
b = 20;
Console.WriteLine(a); // 10 — unchanged!

Value types are copied on assignment. b gets its own independent copy of the value. Changing b does not affect a.

Step 4 — Assigning references

Customer a = new Customer { Name = "Alice" };
Customer b = a;   // b gets a COPY of the REFERENCE (pointer)
b.Name = "Bob";
Console.WriteLine(a.Name); // "Bob" — changed!

Reference types share data. b gets a copy of the reference (the pointer), not the object itself. Both variables point to the same object in memory. Changing the object through b affects a.

Step 5 — Passing parameters

void ModifyValue(int x) { x = 100; }
void ModifyReference(Customer c) { c.Name = "Changed"; }

int num = 5;
ModifyValue(num);
Console.WriteLine(num); // 5 — unchanged

Customer cust = new Customer { Name = "Original" };
ModifyReference(cust);
Console.WriteLine(cust.Name); // "Changed" — changed!

When you pass a value type to a method, you pass a copy. When you pass a reference type, you pass a copy of the reference — the method can modify the original object.

Simple Example

// Value type (struct)
public struct Point
{
    public int X;
    public int Y;
    public Point(int x, int y) { X = x; Y = y; }
}

// Reference type (class)
public class Shape
{
    public string Name { get; set; }
    public Shape(string name) { Name = name; }
}

// Usage
Point p1 = new Point(10, 20);
Point p2 = p1;          // COPY: p2 is a separate copy
p2.X = 99;
Console.WriteLine(p1.X); // 10 — unchanged

Shape s1 = new Shape("Circle");
Shape s2 = s1;          // REFERENCE: s2 points to the same object
s2.Name = "Square";
Console.WriteLine(s1.Name); // "Square" — changed!

Code → Meaning → Result

Real-World Example

Imagine an e-commerce system with an order processing pipeline:

// Value type: immutable money amount
public readonly struct Money
{
    public decimal Amount { get; }
    public string Currency { get; }
    public Money(decimal amount, string currency) => (Amount, Currency) = (amount, currency);

    public Money Add(decimal amount) => new Money(Amount + amount, Currency);
}

// Reference type: order (large, mutable, shared)
public class Order
{
    public int OrderId { get; set; }
    public string CustomerName { get; set; } = "";
    public List<OrderItem> Items { get; } = new();
    public Money Total { get; set; }

    public void AddItem(string name, decimal price, int quantity)
    {
        Items.Add(new OrderItem(name, price, quantity));
        Total = Total.Add(price * quantity);
    }
}

// Usage
Order order = new Order { OrderId = 1024, CustomerName = "Alice" };

// Passing by reference: method modifies the original order
void ApplyDiscount(Order o, decimal discountPercent)
{
    // Modifies the original order object
    decimal discount = o.Total.Amount * (discountPercent / 100);
    o.Total = o.Total.Add(-discount);
}

ApplyDiscount(order, 10); // Order is modified in place
Console.WriteLine(order.Total.Amount); // Discount applied!

Why this matters:

Analogy

Value Type = A physical copy

Value types are like photocopies of a document. If you give someone a photocopy and they write on it, your original stays clean. Each person has their own independent copy.

Reference Type = A shared whiteboard

Reference types are like a whiteboard in a shared room. Everyone gets a pointer to the same whiteboard. If someone writes on it, everyone sees the change. The pointer itself is small (a reference), but the whiteboard can be huge.

Passing by reference (ref) = The original document

When you use ref with a value type, you're passing the original document itself, not a copy. The method can modify it directly.

Under the Hood

What actually happens inside the .NET runtime at the memory level?

MEMORY BEHAVIOR — VALUE VS REFERENCE
1. VALUE TYPE ON THE STACK
Stack:  [int num = 42]  ← the value 42 is stored directly

No heap allocation. No GC pressure. Fast allocation and deallocation.

2. REFERENCE TYPE ON THE HEAP
Stack:  [Customer cust] → 0x1A2B3C
Heap:   0x1A2B3C: [Customer object] { Name="Alice", Id=1024 }

The stack holds a pointer (4 or 8 bytes). The actual object lives on the heap.

3. ASSIGNMENT — VALUE TYPE
int a = 10;
int b = a;   // COPY: 10 is copied from a's stack slot to b's stack slot
b = 20;      // a still has 10, b has 20

The value is bitwise copied from one stack location to another.

4. ASSIGNMENT — REFERENCE TYPE
Customer a = new Customer();  // a = 0x1A2B3C (heap address)
Customer b = a;                // b = 0x1A2B3C (same address, copied)
b.Name = "Bob";                // modifies the object at 0x1A2B3C

The pointer is copied, not the object. Both variables point to the same heap memory.

5. GARBAGE COLLECTION
When no references point to a heap object, the GC can reclaim it.
Value types are reclaimed automatically when they go out of scope.

Reference types have GC overhead; value types do not.

Common Confusion

1. struct vs class

Many beginners assume struct and class are interchangeable. They are not.

Featurestruct (Value)class (Reference)
StorageStack (usually)Heap
AssignmentCopyReference copy
InheritanceCannot inherit from another structCan inherit
Default constructorAlways has a default constructorOnly if you define one
NullabilityCannot be null (unless nullable)Can be null
GC overheadNoneManaged by GC
Use forSmall, immutable dataLarge, shared, mutable data

2. "Pass by value" vs "Pass by reference"

These are different concepts:

void PassByValue(int x) { x = 100; }
void PassByReference(ref int x) { x = 100; }

int a = 5;
PassByValue(a);
Console.WriteLine(a); // 5 — unchanged

PassByReference(ref a);
Console.WriteLine(a); // 100 — changed!

3. Reference types inside value types

If a struct contains a reference type (like a string), copying the struct copies the reference to the string, not the string itself. The struct's value semantics apply to its fields, but reference-type fields still share their objects.

struct Person
{
    public string Name; // reference type inside a value type
}

Person p1 = new Person { Name = "Alice" };
Person p2 = p1;        // p2 gets a COPY of p1 (including the reference)
p2.Name = "Bob";       // BUT: Name is a string — immutable!
Console.WriteLine(p1.Name); // "Alice" — because strings are immutable

// If Name were a mutable class, p2.Name would affect p1.Name

Common Mistakes

Mistake 1 — Assuming a struct is a class

Wrong: Treating a struct like a class and expecting reference semantics.

struct Point { public int X; }
Point p1 = new Point { X = 10 };
Point p2 = p1;
p2.X = 20;
// p1.X is still 10 — you might have expected it to change

Correct: Understand that structs are copied on assignment. If you need reference semantics, use a class.

Mistake 2 — Large structs

Wrong: Creating a struct with many fields (e.g., 100+ bytes) and copying it frequently.

Correct: Use a class for large data to avoid expensive copying. The general recommendation is to keep structs under 16 bytes.

Mistake 3 — Mutating a struct returned from a property

Wrong: Modifying a struct returned by a property does nothing because you're modifying a copy.

struct Vector { public int X; }
class Container { public Vector Position { get; set; } }

var c = new Container();
c.Position.X = 10; //  This modifies a COPY — it does NOT update the stored struct!

Correct: Assign the entire struct or make it immutable.

c.Position = new Vector { X = 10 }; //  Correct

Mistake 4 — Using class for everything

Wrong: Using a class for tiny, immutable data creates unnecessary heap allocations and GC pressure.

Correct: Use a readonly struct for small, immutable data like Point, Money, Color.

When Should I Use It?

Use a value type (struct) when:

Use a reference type (class) when:

When in doubt:

Mental Model

Value Type (struct) = The data is the variable.
Reference Type (class) = The variable points to the data.

Assignment:
· Value type: copy the data
· Reference type: copy the pointer

Passing to a method:
· Value type: method gets a copy → changes don't affect original
· Reference type: method gets a copy of the pointer → changes affect the original object

Memory:
· Value type: stack (fast, no GC)
· Reference type: heap (managed by GC)

Rule of thumb:
· Small + immutable → struct
· Large + mutable + shared → class

Key Takeaway


Check Your Understanding

You've seen how value types and reference types behave differently in memory. Let's see if you can apply this knowledge.

1. What is the fundamental difference between a value type and a reference type in C#?

Show answer

Correct: B

Why B is correct: The core behavioral difference is that value types are copied on assignment (each variable has its own independent copy), while reference types share the same object (assignment copies the reference, not the object).

Why A is incorrect: Value types are typically stored on the stack, and reference types are stored on the heap — not the other way around.

Why C is incorrect: Reference types can be null; value types cannot (unless using nullable types). This is a consequence, not the fundamental difference.

Why D is incorrect: Reference types support inheritance; value types (structs) do not — again, this is a consequence, not the fundamental difference.

Reinforcement: The key behavioral difference is copy semantics (value types) vs reference semantics (reference types).

2. What does the following code output?

struct Point { public int X; }
class Wrapper { public Point Point; }

var w1 = new Wrapper { Point = new Point { X = 5 } };
var w2 = w1;
w2.Point.X = 10;
Console.WriteLine(w1.Point.X);
Show answer

Correct: B

Why B is correct: w1 and w2 both reference the same Wrapper object on the heap. The Point field is a value type stored inline inside the Wrapper object. Since both w1 and w2 point to the same Wrapper instance, modifying w2.Point.X changes the same Point that w1 sees.

Why A is incorrect: Point is a struct, but it is stored inside a reference type (Wrapper). The Wrapper object is shared, so the Point inside it is also shared.

Why C is incorrect: This code compiles and runs without errors.

Why D is incorrect: The Point is initialized to 5, then modified to 10.

Reinforcement: When a value type is a field inside a reference type, it lives on the heap inside that object. The object is shared, so the value-type field is also shared.

3. You are designing a geometry library. You need a Point type with X and Y coordinates. The type should be immutable and used heavily in performance-critical calculations. Which approach is most appropriate?

Show answer

Correct: B

Why B is correct: A readonly struct is a value type that is immutable. It is small (two integers = 8 bytes), will be copied efficiently on the stack, and has no GC overhead — perfect for performance-critical geometry calculations.

Why A is incorrect: A class would allocate each point on the heap, causing GC pressure and slower performance in hot paths. It also allows mutability unless carefully designed.

Why C is incorrect: A record class is a reference type. It would have similar heap allocation overhead as a class. While it provides value-based equality, it's not the best choice for a small, performance-critical value.

Why D is incorrect: dynamic bypasses compile-time type checking and is used for interoperability or dynamic languages — not appropriate here at all.

Reinforcement: Use readonly struct for small, immutable, frequently used data types — they combine value semantics, stack allocation, and immutability.

4. What is the difference between "pass by value" and "pass by reference" in C#?

Show answer

Correct: C

Why C is correct: "Pass by value" means the method receives a copy of the argument's value. Changes inside the method do not affect the caller's variable. "Pass by reference" (using ref, out, or in) means the method receives a reference to the variable itself. Changes inside the method do affect the caller's variable.

Why A is incorrect: They are fundamentally different — one copies the value, the other passes a reference to the variable.

Why B is incorrect: Both value types and reference types can be passed by value (default) or by reference (using ref). For reference types, "pass by value" means passing a copy of the reference — the method can still modify the object, but it cannot reassign the caller's variable.

Why D is incorrect: Performance depends on the size of the data and the context. Passing a large struct by reference can be faster than copying it. This is not a simple rule.

Reinforcement: "Pass by value" vs "pass by reference" is about how the argument is passed to the method, not about whether the type is a value type or reference type.

5. Consider this code. What is the output?

void Update(ref int x) { x = 100; }

int a = 5;
Update(ref a);
Console.WriteLine(a);
Show answer

Correct: B

Why B is correct: The ref keyword passes the a variable by reference, not a copy. The Update method modifies the original variable, changing its value from 5 to 100.

Why A is incorrect: 5 would be the result if a was passed by value (without ref).

Why C is incorrect: This code is perfectly valid C# and compiles without errors.

Why D is incorrect: 0 is the default value for int, but a is explicitly initialized to 5.

Reinforcement: The ref keyword enables pass-by-reference, allowing methods to modify value-type variables directly.

You now have a clear mental model of value vs reference types — from stack vs heap to pass-by-value vs pass-by-reference!


dotnetmadeeasy.com — Learn C# and .NET, the right way.