Value Types vs Reference Types

The data item type determines two key aspects: the memory footprint (number of bits) required to store it, and the data members contained within the type. Additionally, the type dictates whether an object is allocated on the Stack or the Heap in memory.

Types fall into two core categories: Value Types and Reference Types. These two kinds of objects use entirely separate memory storage mechanisms.

1. A value type occupies a single block of memory, which holds the actual raw data directly.

Value type data resides directly on the Stack. Raw values are stored in place, for example int age = 12; the literal 12 is saved straight to this stack memory region

2. Reference types require two distinct memory segments:

  • The first segment stores the actual object data, and this block is always allocated on the Heap;
  • The second segment is a reference address pointing to the location of the heap data. (This address lives on the Stack; back in C language this construct was known as a pointer.)
Reference type data is stored on the Heap, assigned a memory address such as 000001. The Stack then stores this reference address value

The two diagrams above only apply to standalone objects that are not data members of another object.

Important Note

The underlying data body of any reference type object is always placed on the Heap, as shown in the illustrations above.

However, value type instances or reference pointers may live on either the Stack or Heap; their location depends entirely on how your code is structured.

For example:

Take a reference type class named Person with two internal members: one value-type field and one reference-type field.

Many developers mistakenly believe value-type members sit on the Stack, reference pointers live on the Stack, and object bodies go to the Heap (as shown earlier). This assumption is incorrect.

Keep this rule in mind: All raw data belonging to a reference type instance is permanently allocated on the Heap. Every member variable belongs to the object’s underlying data body, so both value-type and reference-type members are stored together within the heap-allocated object.

Sample code:

class Program
{
    static void Main(string[] args)
    {
        int a = 10;
        Person bill = new Person("Bill", 18);
    }

}

class Person
{
    private string name;

    private int age;

    private Phone myPhone;

    public Person(string name, int age)
    {
        this.name = name;
        this.age = age;
        this.myPhone = new Phone();
        this.myPhone.number = 12345;
    }
}   

class Phone
{
    public long number;
}Code language: C# (cs)

Looking at the sample above, the Person class contains built-in fields age and name, plus a custom Phone object. The Phone type itself holds a primitive long variable to store phone numbers.

Readers might see int age inside Person or long number inside Phone and assume these primitive value types live on the Stack. This is not the case. Even though these are basic built-in types, they exist as member fields of a custom class, so they are embedded directly inside the heap-allocated object instance. The memory layout ends up structured like this:

The diagram clearly shows int age and long number are primitive value types, yet as class members they reside on the Heap. The myPhone field stores a reference pointer, proving reference handles can also be stored within heap memory.

Core rule: All member variables of custom classes are allocated on the Heap, regardless of whether they are value or reference types. When a reference-type member such as myPhone exists inside Person, it simply holds a memory address pointing to a separate heap object.

If the Phone class contained another custom object type, its memory block would store another reference address pointing to that secondary custom instance.

Program execution begins inside the Main method. The top-level reference pointer sits on the Stack, and all subsequent referenced objects form a linked chain extending down through heap memory.

References form a linked memory chain

Second example

// Reference type class
class MyType
{
    public int A;       // Value-type member embedded inside the heap MyType instance
    public Person B;    // Reference-type member; pointer stored in heap, object body on separate heap allocation
}

class Person
{
    public string Name;
}

void Test()
{
    // Stack holds the reference pointer for myObj
    MyType myObj = new MyType(); 
    /*
    Heap layout:
    MyType object {
        A: inline stored int value
        B: reference pointer targeting Person instance
    }
    Separate Heap block: Person{ Name: ... }
    */

    // Local value-type variable allocated directly on Stack
    int localNum = 10;
}Code language: C# (cs)

The rules above apply strictly to class member fields, not class methods. When a class function declares local variables or reference objects, the local value types and reference pointers for those instances remain stored on the Stack. Refer to the following code snippet:

Review this extended sample:

using System.ComponentModel.DataAnnotations;

class Program
{
    static void Main(string[] args)
    {
        int a = 10;
        Person bill = new Person("Bill", 18);
        bill.Calculate();
    }

}

class Person
{
    private string name;

    private int age;

    private Phone myPhone;

    public Person(string name, int age)
    {
        this.name = name;
        this.age = age;
        this.myPhone = new Phone();
        this.myPhone.number = 12345;
    }

    public int Calculate()
    {
        int one = 12;
        int two = 23;
        Phone myNewPhone = new Phone();
        return one + two;
    }
}   

class Phone
{
    public long number;
}Code language: C# (cs)

I added a Calculate method to the Person class in this revised example. Inside the method we declare local value-type variables and a reference-type object, which gets invoked from Main. These local variables do not count as Person class members; they belong exclusively to the function scope. Local value types live on the Stack, and reference pointers for local reference objects also reside on the Stack.

Memory layout during execution of bill.Calculate() looks like this:

The blue highlighted section in the diagram shows memory allocation for variables created inside the executing function.

Once the function finishes running, all local variables pop off the Stack, leaving the memory layout as shown below:

After Calculate completes execution and control returns to Main, all local variables declared inside the function are popped from the Stack and discarded. However, the raw data body of myNewPhone (the blue segment in the diagram) stays allocated on the Heap. Only its reference address is removed from the Stack; the actual object data remains in heap memory until the Garbage Collector (GC) reclaims it at a later time.

Value Types vs Reference Types

Previous:

Leave a Reply

Your email address will not be published. Required fields are marked *