Skip to content
Span<T> and Memory<T>
step 1/4

Reading — step 1 of 4

Learn

~1 min readMemory and Performance

Span<T> is a stack-only view into contiguous memory — array, string, native heap, or stackalloc. Zero-copy, bounds-checked, fast.

int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8 };
Span<int> all = arr;
Span<int> middle = arr.AsSpan(2, 4);    // [3, 4, 5, 6]
middle[0] = 99;                          // mutates the original array
// arr is now [1, 2, 99, 4, 5, 6, 7, 8]

ReadOnlySpan<T> — view that can't mutate. Strings convert to ReadOnlySpan<char>:

ReadOnlySpan<char> s = "hello world";
ReadOnlySpan<char> first = s.Slice(0, 5);   // "hello" — no allocation

The killer feature: slicing strings without allocating new strings. Compare to string.Substring() which copies.

Why "stack-only":

  • Span<T> is a ref struct — can't be a field of a class, can't be boxed, can't cross await
  • This is what makes it fast — no heap pressure, no GC tracking
  • The trade-off: limited where you can use it

When you need a heap-friendly version: Memory<T>:

async Task ProcessAsync(Memory<byte> data) {     // can be a field, can cross await
    Span<byte> span = data.Span;                  // get the Span when you need it
    // ... process ...
}

stackalloc — allocate on the stack:

Span<int> small = stackalloc int[10];     // no GC allocation
for (int i = 0; i < 10; i++) small[i] = i * i;

Size-limited, function-scope-limited, but free.

Use cases:

  • Hot-path string parsing (no allocation per token)
  • Zero-copy networking buffers
  • Encoding/decoding with Encoding.UTF8.GetBytes(span, output) overloads
  • Game development — particle systems, collision

Most everyday C# code doesn't need Span<T>. When you're profiling and seeing GC pressure or per-op allocation, that's when to reach for it.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…