Spans are how high-performance .NET code (parsers, ASP.NET Core, serializers) avoids allocations.
Span<T> is a view over a contiguous region of memory: part of an array, a string (as ReadOnlySpan<char>), stack memory from stackalloc, or native memory. Slicing a span (span[2..5] or Slice) creates a new view with no copying and no allocation.
ReadOnlySpan<char> s = "key=value";
var key = s[..s.IndexOf('=')]; // no substring allocationMany APIs accept spans: int.Parse(ReadOnlySpan<char>), MemoryExtensions methods like Split, Trim, SequenceEqual, and Stream.Read(Span<byte>). C# 14 adds implicit conversions so arrays pass to span parameters more naturally.
Span<T> is a ref struct: it can only live on the stack. It can't be a field of a class, be boxed, be captured by lambdas, or (before C# 13) be used across await or in iterators. These rules guarantee it never outlives the memory it points to.
When you need to store a slice on the heap or keep it across awaits, use Memory<T> / ReadOnlyMemory<T> and get a span from .Span when you process it.
Related tools:
stackalloc byte[256]gives a small stack buffer assignable toSpan<byte>.ArrayPool<T>.Shared.Rent/Returnreuses large buffers.string.CreateandISpanFormattablewrite into buffers directly.
Measure first: spans pay off in hot paths and libraries, not ordinary business code.
Example
static int SumCsv(ReadOnlySpan<char> line)
{
int total = 0;
foreach (var range in line.Split(',')) // no string allocations
total += int.Parse(line[range].Trim());
return total;
}
Console.WriteLine(SumCsv("1, 2, 3,40")); // 46
int[] data = [1, 2, 3, 4, 5, 6];
Span<int> middle = data.AsSpan(2, 3); // view over 3,4,5
middle[0] = 99;
Console.WriteLine(string.Join(",", data)); // 1,2,99,4,5,6
Span<byte> buf = stackalloc byte[4]; // stack memory
buf.Fill(0xAB);
Console.WriteLine(Convert.ToHexString(buf)); // ABABABAB
ReadOnlyMemory<char> mem = "hello world".AsMemory(6);
await Task.Yield(); // Memory survives awaits
Console.WriteLine(mem.Span.ToString()); // worldRemember
- Span<T> slices memory without copying
- It is a ref struct: stack-only, no fields in classes or boxing
- Use Memory<T> to store slices on the heap or across awaits
- Span-based APIs avoid substring and array allocations
Watch out for
- Rewriting ordinary code with spans before profiling shows an allocation problem.