C# ReadOnlySpan
last modified April 27, 2025
This tutorial explains how to use the ReadOnlySpan struct in C# to handle memory efficiently. ReadOnlySpan provides a safe way to work with memory slices without allocations.
The ReadOnlySpan struct represents a contiguous region of memory and
is a key feature in .NET that enables efficient handling of data without
creating unnecessary copies. By providing a lightweight, immutable view of
arrays, strings, or raw memory, ReadOnlySpan optimizes performance
and reduces memory overhead. Its design ensures that any modifications are
prohibited, making it an excellent tool for scenarios where data integrity and
immutability are critical. Additionally, ReadOnlySpan is
stack-allocated, contributing to lower garbage collection pressure and better
overall application performance.
ReadOnlySpan is particularly well-suited for tasks like parsing
structured data, processing substrings, or working with data buffers, especially
in high-performance applications. Its type safety ensures that developers can
confidently work with strongly-typed data, while its built-in bounds checking
prevents out-of-range access, reducing the likelihood of runtime errors. These
characteristics make ReadOnlySpan a powerful abstraction for
handling contiguous data in memory-intensive operations, where both reliability
and efficiency are essential.
Basic ReadOnlySpan Example
This example shows how to create a ReadOnlySpan from an array and access its elements without copying the data.
int[] numbers = { 1, 2, 3, 4, 5 };
ReadOnlySpan<int> span = numbers.AsReadOnlySpan();
Console.WriteLine($"First: {span[0]}");
Console.WriteLine($"Length: {span.Length}");
Console.WriteLine($"Slice (2-4): {string.Join(", ", span.Slice(2, 2).ToArray())}");
The program creates a ReadOnlySpan from an integer array and
demonstrates accessing elements and slicing. AsReadOnlySpan creates
a span without copying the array. The example accesses the first element using
indexing, retrieves the span's length, and creates a slice of two elements
starting at index 2.
Since ReadOnlySpan is immutable, it ensures the underlying data
cannot be modified, providing safety while maintaining performance by avoiding
memory allocations.
Slicing Strings with ReadOnlySpan
ReadOnlySpan is useful for efficient string manipulation. This example parses a string without creating substrings.
string text = "Hello, World!";
ReadOnlySpan<char> span = text.AsSpan();
ReadOnlySpan<char> hello = span.Slice(0, 5);
ReadOnlySpan<char> world = span.Slice(7, 5);
Console.WriteLine($"First word: {hello.ToString()}");
Console.WriteLine($"Second word: {world.ToString()}");
The example creates a ReadOnlySpan from a string and extracts
substrings using slicing. AsSpan creates a span over the string's
characters without copying. The program slices the span to extract "Hello" and
"World" by specifying start indices and lengths.
Unlike traditional substring methods, slicing with ReadOnlySpan
avoids allocating new strings, improving performance. The ToString
method converts the spans back to strings for display, but the slicing itself is
allocation-free.
Parsing Numbers with ReadOnlySpan
ReadOnlySpan can parse data efficiently. This example parses
integers from a comma-separated string.
using System;
string data = "42,100,7";
ReadOnlySpan<char> span = data.AsSpan();
List<int> numbers = [];
while (!span.IsEmpty)
{
int commaIndex = span.IndexOf(',');
ReadOnlySpan<char> numberSpan = commaIndex == -1 ? span : span.Slice(0, commaIndex);
numbers.Add(int.Parse(numberSpan));
span = commaIndex == -1 ? [] : span.Slice(commaIndex + 1);
}
Console.WriteLine($"Parsed numbers: {string.Join(", ", numbers)}");
The program parses integers from a string using ReadOnlySpan to
avoid allocations. It creates a span from the input string and iteratively finds
commas to split the data. For each segment, IndexOf locates the
next comma, and Slice extracts the number portion.
int.Parse converts the span to an integer, which is added to a
list.
The span is updated to skip the comma and continue parsing. This approach is efficient because it avoids creating intermediate strings, relying instead on ReadOnlySpan's ability to work directly with the string's memory.
Working with Memory Buffers
ReadOnlySpan can handle raw memory buffers. This example processes a byte buffer as a span.
byte[] buffer = { 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 };
ReadOnlySpan span = buffer;
int firstInt = BitConverter.ToInt32(span.Slice(0, 4));
int secondInt = BitConverter.ToInt32(span.Slice(4, 4));
Console.WriteLine($"First int: {firstInt}");
Console.WriteLine($"Second int: {secondInt}");
The example treats a byte array as a ReadOnlySpan to read integers from a
buffer. The array contains two 32-bit integers in little-endian format.
Slice extracts 4-byte segments, and
BitConverter.ToInt32 converts each segment to an integer. Using
ReadOnlySpan avoids copying the buffer and provides a safe,
bounds-checked way to access the data. This technique is useful for processing
binary data, such as network packets or file formats, where direct memory access
is needed without allocations.
Using ReadOnlySpan with Stack Allocation
ReadOnlySpan can work with stack-allocated memory for maximum efficiency. This example uses stackalloc.
using System;
Span<int> temp = stackalloc int[3];
temp[0] = 10; temp[1] = 20; temp[2] = 30;
ReadOnlySpan<int> span = temp;
Console.WriteLine($"Sum: {span[0] + span[1] + span[2]}");
Console.WriteLine($"Slice (1-2): {string.Join(", ", span.Slice(1, 2).ToArray())}");
The program uses stackalloc to allocate a temporary array on the
stack, which is converted to a ReadOnlySpan. The Span<int> is
filled with values, then cast to a ReadOnlySpan for safe, read-only access.
The example calculates the sum of the elements and creates a slice. Stack
allocation avoids heap allocations, making this approach extremely efficient
for small, short-lived buffers.
ReadOnlySpan ensures the data is accessed safely with bounds
checking, and its immutability prevents accidental modifications to the
underlying memory.
Comparing Strings with ReadOnlySpan
ReadOnlySpan enables efficient string comparisons. This example compares portions of strings.
using System;
string text1 = "Hello, World!";
string text2 = "Hello, Universe!";
ReadOnlySpan<char> span1 = text1.AsSpan(0, 5);
ReadOnlySpan<char> span2 = text2.AsSpan(0, 5);
bool areEqual = span1.SequenceEqual(span2);
Console.WriteLine($"First 5 chars equal: {areEqual}");
The example compares the first five characters of two strings using
ReadOnlySpan. AsSpan creates spans over the relevant portions of
the strings, and SequenceEqual checks if they are identical.
This method is more efficient than creating substrings or using traditional string comparison methods because it operates directly on the string's memory without allocations. This technique is particularly useful in performance-critical scenarios, such as parsing or validating large datasets, where minimizing memory usage and processing time is crucial.
ReadOnlySpan with ReadOnlyMemory
ReadOnlySpan can work with ReadOnlyMemory for flexible
memory management. This example demonstrates conversion.
using System;
int[] numbers = { 1, 2, 3, 4, 5 };
ReadOnlyMemory<int> memory = numbers.AsMemory();
ReadOnlySpan<int> span = memory.Span;
Console.WriteLine($"First: {span[0]}");
Console.WriteLine($"Last: {span[^1]}");
The program converts an array to a ReadOnlyMemory, then extracts a
ReadOnlySpan from it. AsMemory creates a memory object, and the
Span property provides a span for direct access. The example
accesses the first and last elements using indexing. ReadOnlyMemory
is useful for passing memory regions between methods or async operations, while
ReadOnlySpan provides a lightweight, stack-based view for immediate
processing. This combination allows flexible and efficient memory management,
particularly in scenarios involving asynchronous or layered data processing.
Source
ReadOnlySpan Struct Documentation
This tutorial covered using ReadOnlySpan in C# for efficient memory handling, including arrays, strings, buffers, and stack-allocated memory.
Author
List all C# tutorials.