C# Span and Memory — Span, Memory, stackalloc, and Slices
In this tutorial, you will learn about C# Span and Memory. We cover key concepts, practical examples, and best practices to help you master this topic.
C# Span
What You'll Learn
You will master Span
Why It Matters
Before Span
Real-World Use
System.Text.Json parses JSON using spans. ASP.NET Core processes HTTP requests with spans for zero-allocation header parsing. High-performance logging frameworks format messages using spans. Network protocols parse packets with span slices. File processing reads chunks into spanned buffers.
Learning Path
graph LR
A["30: Parallel Programming"] --> B["31: Span Memory"]
B --> C["32: Index Ranges"]
C --> D["33: Top-Level Statements"]
D --> E["34: File I/O"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
style E fill:#4a90d9,stroke:#2c5f8a,color:#fff
Span Basics
// From array
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Span<int> span = array.AsSpan();
Console.WriteLine($"Length: {span.Length}");
// Slicing without allocation
Span<int> firstThree = span[..3]; // 1, 2, 3
Span<int> fromIndex = span[3..]; // 4, 5, 6, 7, 8, 9, 10
Span<int> middle = span[3..7]; // 4, 5, 6, 7
// Modify through span (modifies original array)
firstThree[0] = 99;
Console.WriteLine($"array[0] = {array[0]}"); // 99
Span from String
string text = "Hello, World!";
ReadOnlySpan<char> charSpan = text.AsSpan();
ReadOnlySpan<char> hello = charSpan[..5];
ReadOnlySpan<char> world = charSpan[7..^1];
Console.WriteLine(hello.ToString()); // Hello
Console.WriteLine(world.ToString()); // World
// Zero-allocation substring search
bool ContainsWord(ReadOnlySpan<char> text, ReadOnlySpan<char> word)
=> text.Contains(word, StringComparison.OrdinalIgnoreCase);
Console.WriteLine(ContainsWord(text.AsSpan(), "WORLD")); // True
Stackalloc
Stack-allocated memory (no heap allocation):
// Small buffer on stack (fast, no GC)
Span<byte> buffer = stackalloc byte[256];
// Initialize
buffer.Fill(0);
buffer[0] = 42;
// Format into stackalloc'd buffer
Span<char> charBuffer = stackalloc char[64];
bool success = DateTime.UtcNow.TryFormat(charBuffer, out int written,
"yyyy-MM-dd HH:mm:ss");
Console.WriteLine(charBuffer[..written].ToString());
// Conditional stackalloc (only small arrays on stack)
int size = 1000;
Span<int> data = size <= 1024
? stackalloc int[size]
: new int[size];
Memory
Unlike Span
// Memory<T> is a heap-safe wrapper
Memory<int> memory = new int[] { 1, 2, 3, 4, 5 };
// Get a Span from Memory (synchronous)
Span<int> span = memory.Span;
// Slice Memory
Memory<int> slice = memory[2..];
// Memory<T> can be used in async methods
async Task ProcessMemoryAsync(Memory<byte> buffer)
{
// Pin the Memory to get a Span for synchronous work
// (Cannot use Span directly in async)
int length = buffer.Length;
await Task.Delay(10);
// After await, get Span again
Span<byte> span = buffer.Span;
span[0] = 42;
}
ReadOnlySpan and ReadOnlyMemory
ReadOnlyMemory<char> rom = "Hello, Memory!".AsMemory();
ReadOnlySpan<char> ros = rom.Span;
// Parse operations
ReadOnlySpan<char> input = "42,3.14,hello".AsSpan();
int comma1 = input.IndexOf(',');
int comma2 = input.LastIndexOf(',');
int number = int.Parse(input[..comma1]);
double pi = double.Parse(input[(comma1 + 1)..comma2]);
string word = new string(input[(comma2 + 1)..]);
Console.WriteLine($"{number}, {pi}, {word}");
Span in Practice: Parser
ReadOnlySpan<char> ParseCsvLine(ReadOnlySpan<char> line)
{
int commaPos = line.IndexOf(',');
if (commaPos < 0) return line.Trim();
return line[..commaPos].Trim();
}
string csv = "apple,banana,cherry";
ReadOnlySpan<char> csvSpan = csv.AsSpan();
int pos;
while ((pos = csvSpan.IndexOf(',')) >= 0)
{
var field = csvSpan[..pos].Trim();
Console.WriteLine($"Field: {field.ToString()}");
csvSpan = csvSpan[(pos + 1)..];
}
if (csvSpan.Length > 0)
Console.WriteLine($"Field: {csvSpan.ToString()}");
// Zero-allocation CSV field extraction
ReadOnlySpan<char> line = "123,John,Doe,30".AsSpan();
var fields = new List<string>();
while (line.Length > 0)
{
int commaIdx = line.IndexOf(',');
var field = commaIdx < 0 ? line : line[..commaIdx];
fields.Add(new string(field));
line = commaIdx < 0 ? ReadOnlySpan<char>.Empty : line[(commaIdx + 1)..];
}
Console.WriteLine(string.Join(" | ", fields));
Binary Data with Span
byte[] binaryData = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
Span<byte> bytes = binaryData.AsSpan();
// Read as different types
int intValue = System.Buffers.Binary.BinaryPrimitives
.ReadInt32LittleEndian(bytes);
short shortValue = System.Buffers.Binary.BinaryPrimitives
.ReadInt16LittleEndian(bytes[4..]);
Console.WriteLine($"Int: {intValue}, Short: {shortValue}");
// Write to span
Span<byte> outBuffer = stackalloc byte[8];
System.Buffers.Binary.BinaryPrimitives
.WriteInt32LittleEndian(outBuffer, 123456);
System.Buffers.Binary.BinaryPrimitives
.WriteInt32LittleEndian(outBuffer[4..], 789012);
Console.WriteLine($"Written: {BitConverter.ToString(outBuffer.ToArray())}");
ArrayPool for Buffer Reuse
using System.Buffers;
byte[] rented = ArrayPool<byte>.Shared.Rent(1024);
try
{
Span<byte> buffer = rented.AsSpan(0, 1024);
// Use buffer
buffer[0] = 255;
Console.WriteLine($"Buffer[0]: {buffer[0]}");
}
finally
{
ArrayPool<byte>.Shared.Return(rented);
}
Common Mistakes
Mistake 1: Using Span in Async Methods
Span
Mistake 2: Returning Span from Methods
Spans can only be returned if the underlying memory is stackalloc'd or if the method is ref-returning. Usually, return Memory
Mistake 3: Not Checking Span Length Before Slicing
Slicing beyond the span length throws IndexOutOfRangeException. Always check .Length before slicing.
Mistake 4: Forgetting That Span Modifies the Original Data
Span is a view, not a copy. Modifications through the span change the original array or memory.
Mistake 5: Using Span for Large Persistent Buffers
Span is optimized for temporary, short-lived use. For long-lived buffers, use Memory
Mistake 6: Not Using stackalloc for Small Temporary Buffers
For buffers under ~1KB, stackalloc is faster than heap allocation. Use it in hot paths.
Practice Questions
- What is the difference between Span
and Memory ? - When would you use stackalloc instead of new T[]?
- Why can't Span
be used in async methods? - How does slicing with Span
avoid memory allocation? - Write a method that parses a space-separated list of integers using Span
without allocations.
Challenge
Create a high-performance CSV parser that uses ReadOnlySpan
FAQ
Mini Project
Create a high-performance log parser:
using System.Buffers;
class LogParser
{
public static void ParseLogEntries(ReadOnlySpan<char> logContent)
{
int count = 0;
var remaining = logContent;
while (remaining.Length > 0)
{
int lineEnd = remaining.IndexOf('\n');
ReadOnlySpan<char> line;
if (lineEnd < 0)
{
line = remaining.Trim();
remaining = ReadOnlySpan<char>.Empty;
}
else
{
line = remaining[..lineEnd].Trim();
remaining = remaining[(lineEnd + 1)..];
}
if (line.Length == 0) continue;
count++;
// Parse: [LEVEL] timestamp message
if (line[0] == '[')
{
int closeBracket = line.IndexOf(']');
if (closeBracket > 0)
{
var level = line[1..closeBracket];
var rest = line[(closeBracket + 1)..].Trim();
int spacePos = rest.IndexOf(' ');
var timestamp = spacePos > 0 ? rest[..spacePos] : rest;
var message = spacePos > 0 ? rest[(spacePos + 1)..] : ReadOnlySpan<char>.Empty;
Console.WriteLine($"Level: {level.ToString()}, TS: {timestamp.ToString()}, Msg: {message.ToString()}");
}
}
}
Console.WriteLine($"\nParsed {count} lines");
}
}
string logData = """
[INFO] 2026-06-28T10:00:00 Server started
[WARN] 2026-06-28T10:01:00 Memory usage high
[ERROR] 2026-06-28T10:02:00 Connection failed: timeout
[INFO] 2026-06-28T10:03:00 Retry attempt 1
""";
LogParser.ParseLogEntries(logData.AsSpan());
What's Next
You have mastered Span
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro