What is `Span<T>` and `Memory<T>`? When should you use them?
Quick Answer
`Span<T>` is a `ref struct` giving a type-safe, allocation-free view over contiguous memory (arrays, stack memory, or native memory), enabling slicing without copying. Because it's stack-only it can't be stored on the heap, boxed, or used across `await`; `Memory<T>` is the heap-friendly counterpart that can be stored in fields and awaited. Use them in performance-critical code (parsing, buffers) to slice and process data without extra allocations.
Detailed Answer
Span<T>:
A ref struct that provides a type-safe and memory-safe representation of a contiguous region of arbitrary memory.
Key Characteristics:
- Stack-only type (ref struct)
- Zero allocation
- Can point to stack memory, managed heap, or native memory
- Cannot be used in async methods
- Cannot be stored as a field in regular classes
Example:
// Working with arrays without allocation
public int Sum(Span numbers)
{
int sum = 0;
foreach (int num in numbers)
{
sum += num;
}
return sum;
}
// Can be called with different memory sources
int[] array = {1, 2, 3, 4, 5};
Sum(array); // Works with array
Sum(array.AsSpan(1, 3)); // Works with slice
Span stackArray = stackalloc int[5];
Sum(stackArray); // Works with stack memory
String Manipulation:
public ReadOnlySpan ExtractUsername(string email)
{
int atIndex = email.IndexOf('@');
return email.AsSpan(0, atIndex); // No allocation!
}
Memory<T>:
Similar to Span<T> but can be stored on the heap and used in async methods.
Key Characteristics:
- Can be stored as a field in classes
- Can be used in async methods
- Slightly more overhead than Span<T>
- Provides
.Spanproperty to get a Span<T>
Example:
public class BufferProcessor
{
private Memory _buffer;
public async Task ProcessAsync()
{
// Memory can be used across await
await Task.Delay(100);
// Get Span when doing actual work
Span span = _buffer.Span;
ProcessData(span);
}
private void ProcessData(Span data)
{
// Fast processing without allocations
}
}
When to Use:
Use Span<T> when:
- Working with buffers in synchronous code
- Need maximum performance and zero allocations
- Manipulating strings or arrays without creating copies
- Working with stack-allocated memory
Use Memory<T> when:
- Need to store reference to memory as a field
- Working in async methods
- Need to pass memory across async boundaries
- Building APIs that might be used in async contexts
Use ReadOnlySpan<T>/ReadOnlyMemory<T> when:
- You don't need to modify the data
- Provides additional safety guarantees
Performance Benefits:
// Traditional approach - allocations!
public string[] SplitData(string data)
{
return data.Split(','); // Allocates array + strings
}
// Span approach - zero allocations!
public void SplitData(ReadOnlySpan data, Span ranges)
{
int rangeIndex = 0;
int start = 0;
for (int i = 0; i < data.Length; i++)
{
if (data[i] == ',')
{
ranges[rangeIndex++] = start..i;
start = i + 1;
}
}
ranges[rangeIndex] = start..data.Length;
}