C#  

From Stack to Span<T> — A Recap into C#’s Memory

Introduction

When I started diving into performance in C#, I realised there’s a hidden layer behind every line of code, particularly in terms of memory usage. The way we store, access, and pass data around can mean the difference between a smooth app and a laggy mess.

So, let’s go on a recap starting from the basics and building up to some of the coolest features in modern C#, like Span<T>.

The Foundation — Stack and Heap

Before we can talk about Span<T> or even “contiguous memory,” we need to know where our data lives.

Stack

  • Fast, short-lived storage
  • Holds local variables, method parameters, and most value types
  • Works like a stack of plates (Last In, First Out)
  • Gets cleared automatically when a method ends

Heap

  • Slower but flexible storage
  • Holds reference types (objects, arrays, strings)
  • Managed by the garbage collector (GC)
  • Data here can live far longer than the method that created it

Think of the stack as a desk in front of you — quick access but limited space. The heap is a big warehouse, but you need a forklift (GC) to manage it.

struct — The Lightweight Data Container

Now that we know about the stack, let’s meet its close friend: struct.

A struct is,

  • A value type
  • Usually stored on the stack
  • Passed by copy, not by reference
  • Great for small, lightweight data
public struct Point(int x, int y)
{
    public int X = x, Y = y;
}

Point p1 = new(3, 4);
Point p2 = p1; // Copy — p2 is independent
p2.X = 10;
Console.WriteLine($"p1: ({p1.X}, {p1.Y})");
Console.WriteLine($"p2: ({p2.X}, {p2.Y})");
///////////////////////////////////////////////////////
Output:
p1: (3, 4)
p2: (10, 4)

This clearly shows that p1 stays unchanged after modifying p2, proving that structs are copied by value.

Contiguous Memory — The Secret to Speed

Some data structures store items in contiguous memory, meaning all elements sit right next to each other in one block.

[ 10 ][ 20 ][ 30 ][ 40 ]

Why is this good?

  • The CPU can prefetch data efficiently
  • Slicing (taking part of the data) is faster
  • Fewer memory lookups

Why List<T> Is Contiguous… But Not Quite?

At first glance, List<T> seems contiguous; it wraps an internal array.

But there’s a catch.

  • The List object itself lives on the heap
  • It holds a reference to an internal array (which is contiguous)
  • When the List grows, it allocates a new array and copies elements over

So, List<T> can’t directly be treated as a block of memory the way arrays or Span<T> can.

Span<T> — Memory Manipulation Without the Cost

Span<T> lets you work with slices of contiguous memory without creating new arrays or copying data.

Traditionally, in C#,

  • If you wanted part of an array, you’d copy it to a new array.
  • That meant extra allocations → more work for the GC.

Span<T> changes this.

  • It represents a contiguous region of memory (stack, heap, or unmanaged memory).
  • You can create a slice without copying.
    int[] numbers = { 10, 20, 30, 40, 50 };
    Span<int> part = numbers.AsSpan(1, 3); // points to 20, 30, 40
  • Changing part[0] also changes numbers[1] — they’re literally pointing to the same memory.
  • No new array, no GC hit, no wasted CPU cycles.

Why Span<T> Is a ref struct?

Unlike normal structs, Span<T> is a ref struct, which means.

  • It must live on the stack
  • Can’t be stored in the heap
  • Can’t be used in async methods or lambdas

Why?

Because Span<T> points directly into memory, letting it move to the heap would risk dangling pointers and unsafe memory access.

Final Takeaways

By understanding,

  • Stack vs Heap: where data lives
  • Structs: lightweight value containers
  • Contiguous Memory: Why Sequential Storage Is Fast
  • List<T> limitations: when “contiguous” isn’t quite
  • Span<T>: how to slice memory without allocations

Thank You, and Stay Tuned for More!

More Articles from my Account.