Span
Span<T>
(introduced in C# 7.2) and its sibling Memory<T>
let you work with contiguous blocks of data without extra allocations or copies—all while staying within C#’s safety rules. Think of them as zero-cost, slice-style views over memory that can point to the stack, the managed heap, or even unmanaged buffers.
Typical use cases include:
Span<T>
at a glanceHighlight | What it means |
---|---|
Value type (ref struct ) | Lives on the stack; passing it around is almost free. |
Stack-only | Cannot escape the current method, so the JIT knows exactly when it dies. |
Universal view | The same API slices arrays, stackalloc blocks, and unmanaged memory. |
int[] data = { 1, 2, 3, 4, 5 };
Span<int> span = data; // entire array
Span<int> middle = span[1..4]; // elements 1-3
middle[0] = 99; // mutates data[1]
Console.WriteLine(string.Join(", ", data));
// 1, 99, 3, 4, 5
The slice is just a lightweight view; no element has been copied.
stackalloc
Span<byte> buffer = stackalloc byte[256];
for (int i = 0; i < buffer.Length; i++)
buffer[i] = (byte)i;
You get 256 bytes on the stack—zero GC pressure, automatic cleanup when the method returns.
Memory<T>
Span<T>
cannot survive beyond the current stack frame or cross an await
. When you need that flexibility, switch to Memory<T>
(or its read-only counterpart ReadOnlyMemory<T>
).
Aspect | Span<T> | Memory<T> |
---|---|---|
Stored on | Stack only | Heap or stack |
Works across await / iterators | ✘ | ✔ |
Mutability | Read/write | Read/write (ReadOnlyMemory<T> is read-only) |
public async Task ParseAsync(ReadOnlyMemory<byte> packet)
{
var header = packet[..4];
var body = packet[4..];
await Task.Yield(); // safe: Memory survives across awaits
Console.WriteLine(
$"Header={BitConverter.ToUInt32(header.Span)} Body bytes={body.Length}");
}
Inside synchronous code blocks you can still call .Span
to get the fast, stack-only view.
ReadOnlySpan<T>
for input parameters to make intent crystal-clear and prevent accidental writes.[..]
or Slice
) is O(1).Span<T>
pairs naturally with TryParse
/ TryFormat
APIs for allocation-free conversions.await
, promote to Memory<T>
early and convert back to .Span
only for short, synchronous work.Span<T>
to code that might outlive the current method. Respect those errors—they prevent use-after-free bugs.Memory<T>
instances as long-lived fields can fragment the LOH. Pool or reuse where possible.MemoryMarshal.CreateSpan
lets you wrap raw pointers, but handle lifetime manually and beware of pinning costs.