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:
boolcharbyteshortintlongfloatdoubledecimal
User-defined value types include:
structenum
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:
classstring- 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:
productstores a reference.- The actual
Productobject stores theNameproperty.
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
| Feature | Value Type | Reference Type |
|---|---|---|
| Stores | The actual data | A reference to an object |
| Assignment | Copies the value | Copies the reference |
| Default value | Zero-initialized value | null |
Can be null | Only if nullable (int?) | Yes |
| Examples | int, double, bool, struct, enum | class, 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
Nameproperty.
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.