SyntaxStudy
Sign Up
C# Value Types vs Reference Types in Depth
C# Beginner 1 min read

Value Types vs Reference Types in Depth

Understanding how value types and reference types are stored is essential for writing efficient, correct C# code. Value types such as `int`, `double`, `struct`, and `enum` store their data directly in the memory location that holds the variable. When you copy a value type, you get an independent duplicate. Reference types such as classes, arrays, delegates, and strings store a reference (pointer) to heap-allocated data. Two variables can reference the same object, so a mutation through one variable is visible through the other. Strings are reference types but behave like values because they are immutable; every modification creates a new string. Boxing converts a value type to an `object` reference (heap-allocates a copy), and unboxing extracts the value back. Excessive boxing in hot paths hurts performance; using generic collections like `List` instead of `ArrayList` avoids it entirely.
Example
// Demonstrating value-type vs reference-type copy semantics

// --- Struct (value type) ---
struct Celsius
{
    public double Degrees;
    public Celsius(double d) => Degrees = d;
    public override string ToString() => $"{Degrees}°C";
}

var boiling = new Celsius(100);
var copy    = boiling;         // independent copy
copy.Degrees = 0;
Console.WriteLine(boiling);   // 100°C  — unchanged
Console.WriteLine(copy);      // 0°C

// --- Class (reference type) ---
class Temperature
{
    public double Degrees { get; set; }
    public Temperature(double d) => Degrees = d;
    public override string ToString() => $"{Degrees}°C";
}

var t1 = new Temperature(100);
var t2 = t1;                   // both point to the same object
t2.Degrees = 0;
Console.WriteLine(t1);        // 0°C — mutated via t2!
Console.WriteLine(t2);        // 0°C

// --- String immutability ---
string s1 = "hello";
string s2 = s1;
s2 = s2.ToUpper();            // creates a new string
Console.WriteLine(s1);        // "hello" — unchanged
Console.WriteLine(s2);        // "HELLO"

// --- Span<T> for stack-based slicing (no heap alloc) ---
Span<int> nums = stackalloc int[] { 10, 20, 30, 40 };
var slice = nums[1..3];       // [20, 30]
Console.WriteLine(slice[0]);  // 20