What is Span<T> and Memory<T>?
Span<T> and Memory<T> are value types introduced in .NET Core 2.1 that provide type-safe and memory-safe representation of a contiguous region of arbitrary memory. They enable zero-copy slicing of arrays, strings, and other memory buffers without allocating new objects on the heap. Span<T> is a stack-only type (ref struct) that provides direct access to memory, while Memory<T> is a heap-allocated wrapper that can be stored in fields and used across async boundaries.
How to Use Them
Use Span<T> for synchronous operations where you need high-performance memory access, and Memory<T> for scenarios involving async operations or when you need to store the reference in a field. They’re particularly useful for parsing, string manipulation, and buffer operations where you want to avoid unnecessary allocations.
Example: String Processing with Span<T>
using System;
using System.Buffers;
public class SpanExample
{
public static void ProcessLargeString()
{
// Original string - this is our source data
string largeText = "Hello,World,From,Span,Processing,Example,Data";
// Convert string to ReadOnlySpan<char> - no allocation, just a view
ReadOnlySpan<char> textSpan = largeText.AsSpan();
// Split the string using spans - no string allocations
SplitStringWithSpan(textSpan, ',');
}
private static void SplitStringWithSpan(ReadOnlySpan<char> input, char delimiter)
{
// Keep track of current position in the span
int start = 0;
// Iterate through each character in the span
for (int i = 0; i < input.Length; i++)
{
// Check if current character is our delimiter
if (input[i] == delimiter)
{
// Create a slice from start to current position
// This creates a new span view, no memory allocation
ReadOnlySpan<char> segment = input.Slice(start, i - start);
// Process the segment (convert to string only when needed)
Console.WriteLine($"Segment: {segment.ToString()}");
// Move start position past the delimiter
start = i + 1;
}
}
// Handle the last segment (after the final delimiter)
if (start < input.Length)
{
ReadOnlySpan<char> lastSegment = input.Slice(start);
Console.WriteLine($"Last segment: {lastSegment.ToString()}");
}
}
// Example with Memory<T> for async operations
public static async Task ProcessDataAsync()
{
// Rent a buffer from the array pool - reuses existing arrays
byte[] rentedArray = ArrayPool<byte>.Shared.Rent(1024);
// Create Memory<T> wrapper around the rented array
Memory<byte> memory = rentedArray.AsMemory(0, 512); // Use only first 512 bytes
// Memory<T> can be used across async boundaries
await FillBufferAsync(memory);
// Process the data using Span<T> (synchronous operations)
ProcessBuffer(memory.Span);
// Return the rented array to the pool
ArrayPool<byte>.Shared.Return(rentedArray);
}
private static async Task FillBufferAsync(Memory<byte> buffer)
{
// Simulate async I/O operation
await Task.Delay(10);
// Fill buffer with sample data
for (int i = 0; i < buffer.Length; i++)
{
buffer.Span[i] = (byte)(i % 256);
}
}
private static void ProcessBuffer(Span<byte> buffer)
{
// Direct memory access for high-performance processing
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = (byte)(buffer[i] * 2); // Double each value
}
}
}