What is `Span<T>` and `Memory<T>`? When should you use them?

4 minadvanced.NETSpanMemoryperformance

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 .Span property 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;
}