Understanding Value Types and Reference Types in C#

One of the first concepts every C# developer encounters is the distinction between value types and reference types. Although both are used to store data, they behave very differently when you assign them to variables, pass them to methods, or allocate memory.

Understanding this difference is essential because it affects:

  • Memory allocation
  • Performance
  • Object lifetime
  • Parameter passing
  • Program correctness

In this article, you’ll learn how value types and reference types work, how they are stored in memory, and when to use each one.


What Is a Value Type?

A value type stores its data directly inside the variable itself.

Each variable owns its own copy of the data. If you assign one value type variable to another, the data is copied.

Built-in value types include:

  • bool
  • char
  • byte
  • short
  • int
  • long
  • float
  • double
  • decimal

User-defined value types include:

  • struct
  • enum

Example

int number = 10;

Console.WriteLine(number);

The variable number directly contains the value 10.

Unlike reference types, there is no separate object that stores the integer.


Value Types Are Copied

When you assign one value type variable to another, C# copies the value.

After the assignment, each variable has its own independent copy of the data.

int x = 10;
int y = x;

y = 20;

Console.WriteLine(x);
Console.WriteLine(y);

Output

10
20

When y is later changed to 20, the value stored in x remains unchanged because the two variables are completely independent.

This behavior is known as copy by value.


Value Type Example with Structures

Structures (struct) are also value types.

public struct Size
{
    public int Width;
    public int Height;
}

Size size1 = new Size
{
    Width = 800,
    Height = 600
};

Size size2 = size1;

size2.Width = 1024;

Console.WriteLine(size1.Width);
Console.WriteLine(size2.Width);

Output

800
1024

Although Size contains multiple fields, assigning size1 to size2 copies every field.

Changing size2 afterward does not affect size1.


What Is a Reference Type?

A reference type stores a reference to an object rather than the object itself.

The actual object lives elsewhere in managed memory, while the variable stores only a reference that identifies where that object is located.

Common reference types include:

  • class
  • string
  • arrays
  • delegates
  • interfaces
  • records (reference records)

Example

public class Product
{
    public string Name { get; set; } = "";
}

Creating an instance:

Product product = new Product();

product.Name = "Keyboard";

In this example:

  • product stores a reference.
  • The actual Product object stores the Name property.

Reference Types Share the Same Object

Unlike value types, assigning one reference type variable to another does not create a copy of the object.

Instead, both variables refer to the same object.

Product product1 = new Product();
product1.Name = "Keyboard";

Product product2 = product1;

product2.Name = "Mouse";

Console.WriteLine(product1.Name);
Console.WriteLine(product2.Name);

Output

Mouse
Mouse

After the assignment, the variables conceptually look like this:

product1 ───┐


         Product
      Name = "Mouse"


product2 ───┘

Since both variables refer to the same object, modifying the object through one variable is immediately visible through the other.


Comparing Value Types and Reference Types

FeatureValue TypeReference Type
StoresThe actual dataA reference to an object
AssignmentCopies the valueCopies the reference
Default valueZero-initialized valuenull
Can be nullOnly if nullable (int?)Yes
Examplesint, double, bool, struct, enumclass, string, arrays, delegates

Why Does C# Have Both?

Each category serves a different purpose.

Value types are ideal for representing small pieces of data that are copied frequently, such as:

  • Numbers
  • Dates
  • Coordinates
  • Colors
  • Mathematical values

Reference types are designed for representing objects with identity, such as:

  • Products
  • Customers
  • Orders
  • Employees
  • Files
  • Database entities

Imagine a product object containing many properties. If every assignment copied the entire object, programs would waste both memory and CPU time.

Instead, C# copies only the reference, allowing multiple variables to refer to the same object efficiently.


Creating an Independent Object

If you want two separate objects, you must create two separate instances.

Product product1 = new Product();
product1.Name = "Keyboard";

Product product2 = new Product();
product2.Name = product1.Name;

product2.Name = "Mouse";

Console.WriteLine(product1.Name);
Console.WriteLine(product2.Name);

Output

Keyboard
Mouse

Now the variables refer to different objects.

The Special Case of string

The string type often surprises beginners.

Although string is a reference type, it is also immutable, meaning its contents cannot be modified after the string has been created.

For example:

string text1 = "Hello";
string text2 = text1;

text2 += " World";

Console.WriteLine(text1);
Console.WriteLine(text2);

Output

Hello
Hello World

It may look like text2 modified the original string, but that’s not what happened.

Instead, C# created a new string object containing "Hello World" and updated text2 to reference that new object.

The original "Hello" string remains unchanged.


Memory Allocation: Stack vs. Heap

One of the biggest differences between value types and reference types is how they are stored in memory.

Before diving into the details, it’s important to understand that the concepts of stack and heap are useful as a mental model. Modern versions of .NET include many runtime and compiler optimizations, so the actual implementation may differ from the simplified diagrams you’ll often see.

Nevertheless, understanding the conceptual model makes it much easier to reason about how C# variables behave.


The Stack

The stack is a region of memory used for managing method execution.

Whenever a method is called, the runtime creates a stack frame that contains information such as:

  • Local variables
  • Method parameters
  • Return address

When the method finishes executing, its stack frame is automatically removed.


The Heap

The heap is a region of managed memory used to store objects.

When you create an instance of a class using the new operator, the runtime allocates memory for that object on the heap.



Memory Allocation for Value Types

Value types usually store their data directly where the variable exists.

For example:

Size screen = new Size
{
    Width = 800,
    Height = 600
};

Conceptually:

Stack

screen
 ├── Width = 800
 └── Height = 600

Notice that there is no separate object referenced by screen.

The fields are part of the variable itself.


Memory Allocation for Reference Types

Reference types are allocated differently from value types. When you instantiate a class, C# allocates memory for the object on the managed heap and stores a reference to that object in the variable.

Product product = new Product();
product.Name = "Keyboard";

This single statement results in two allocations:

  • A reference variable named product.
  • A Product object that contains the Name property.

Conceptually, the memory looks like this:

Stack                      Heap

product ───────────────►  Product
                           Name = "Keyboard"

Notice that the variable itself does not contain the object. Instead, it contains a reference that allows the runtime to locate the object in memory.

This distinction explains why assigning one reference variable to another doesn’t duplicate the object. Only the reference is copied, allowing multiple variables to access the same instance.




A Note About Value Types Inside Reference Types

A common misconception is that value types always live on the stack.

That’s not always true.

Consider the following class:

public class Rectangle
{
    public int Width;
    public int Height;
}

When you create an instance:

Rectangle rectangle = new Rectangle();

rectangle.Width = 800;
rectangle.Height = 600;

The Rectangle object is stored on the heap because it is a reference type.

Its Width and Height fields are part of that object, so they are also stored within the heap-allocated object—not separately on the stack.

Similarly, value types can also be stored:

  • As fields inside class objects
  • Inside arrays
  • As boxed values

A more accurate way to think about value types is:
“A value type stores its value directly wherever the variable or field is allocated.”


Garbage Collection

Unlike many programming languages, C# developers do not manually free memory allocated for objects.

Instead, .NET uses a Garbage Collector (GC).

When no variables reference an object anymore, it becomes eligible for garbage collection.

For example:

Product product = new Product();

product = null;

If there are no other references to that Product object, the runtime can reclaim its memory during a future garbage collection cycle.

The exact timing is determined by the runtime and should not be relied upon.


Common Misconceptions

Understanding a few common misconceptions can help you build a more accurate mental model of how C# manages memory.

“Value types are always stored on the stack.”

Not always.

Value types are stored directly where they are declared. That location may be on the stack, inside an object on the heap, or in another storage location depending on the context.


“Reference types are stored on the heap.”

The objects created from reference types are typically allocated on the managed heap.

However, the reference variable itself is stored wherever the variable is declared—for example, as a local variable in a method or as a field in another object.


“Assigning a reference type copies the object.”

It doesn’t.

Only the reference is copied.

Both variables refer to the same object until a new object is explicitly created.