Ever felt like your application is doing more work than necessary just to move a few bytes around? You might be missing out on one of C#’s most powerful performance tools: Span<T>. Let me show you how this little slice of memory magic can shave milliseconds off your hot paths.
What Is Span?
Span<T> is a value type introduced in C# 7.2 that represents a contiguous region of arbitrary memory. It’s like a super-efficient array wrapper that doesn’t allocate on the heap.
Unlike arrays or lists, Span<T> is stack-allocated and provides a safe, fast way to work with slices of data—whether that’s arrays, strings, or unmanaged memory.
Key characteristics:
- No heap allocations
- Fast slicing and slicing operations
- Type-safe and memory-safe
- Works with
stackallocfor high-performance scenarios
The Performance Boost: Why Use Span?
Let’s be blunt: allocations are expensive. Every time you create a new array or list, you add pressure on the garbage collector.
With Span<T>, you:
- Avoid allocations for temporary data structures
- Work directly with memory for tasks like parsing, encoding, and transformations
- Minimize copying of data
Here’s a microbenchmark scenario:
string input = "1234567890";
ReadOnlySpan<char> span = input;
ReadOnlySpan<char> sliced = span.Slice(2, 5);
Console.WriteLine(sliced.ToString()); // Output: 34567No new string is created—you’re working on the same memory.
Getting Started: Syntax & Basic Usage
Span<int> numbers = stackalloc int[5] { 1, 2, 3, 4, 5 };
Span<int> slice = numbers.Slice(1, 3);
foreach (var number in slice)
Console.WriteLine(number);Explanation:
stackallocallocates memory on the stack (not heap!)Slice(1, 3)returns elements 2, 3, and 4- No garbage collector involvement
Want to operate on parts of a byte array?
byte[] buffer = new byte[100];
Span<byte> bufferSpan = buffer.AsSpan(10, 50);Advanced Examples & Use Cases
Parsing integers from a large file buffer:
ReadOnlySpan<byte> line = Encoding.UTF8.GetBytes("42,65,89,100");
int sum = 0;
foreach (var part in line.ToString().Split(','))
sum += int.Parse(part);
Console.WriteLine(sum);Better yet:
ReadOnlySpan<byte> line = Encoding.UTF8.GetBytes("42,65,89,100");
ReadOnlySpan<char> span = Encoding.UTF8.GetString(line).AsSpan();
int sum = 0;
int comma;
do
{
comma = span.IndexOf(',');
var segment = comma != -1 ? span.Slice(0, comma) : span;
sum += int.Parse(segment);
span = comma != -1 ? span.Slice(comma + 1) : Span<char>.Empty;
} while (!span.IsEmpty);
Console.WriteLine(sum);Avoids allocations by skipping string.Split.
Unsafe interop/memory manipulation:
unsafe
{
int* ptr = stackalloc int[3] { 1, 2, 3 };
Span<int> span = new Span<int>(ptr, 3);
span[0] = 10;
Console.WriteLine(span[0]); // Output: 10
}Best Practices for Using Span
- Use
Span<T>when you need high-performance, temporary views over data - Combine with
stackallocfor max performance - Use
ReadOnlySpan<T>when you don’t need to modify data - Ideal for parsers, encoders, formatters, protocol readers
- Use in performance-critical hot paths to reduce allocations
Common Pitfalls & Limitations
- Can’t be stored in fields of classes (unless in ref structs)
- Can’t be boxed or used with async methods
- Short-lived: only live as long as the stack frame
- Misusing
Span<T>can cause undefined behavior or exceptions
FAQ: Common Span Questions
No. Spans can’t be captured across await. Use memory pooling or arrays instead.
You can’t unless it’s returned immediately from a ref struct or within the same stack frame.
Span<T> is stack-only and fast; Memory<T> is heap-safe and can be used in async methods.
Conclusion: Span is Small but Mighty
If you’re chasing those last drops of performance or want to avoid heap allocations without resorting to unsafe code, Span<T> is your friend. I’ve used it in data pipelines, serializers, and even to shave off GC pressure in tight loops. It’s one of those underused gems in C#.
Give it a try—your performance metrics will thank you.
What’s the most clever way you’ve used Span<T> in your code? Share it in the comments below!
