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 aref 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…