Skip to content

C# Span and Memory — Span, Memory, stackalloc, and Slices

DodaTech Updated 2026-06-28 6 min read

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 and Memory are stack-safe value types that provide allocation-free views over contiguous memory regions, enabling high-performance string processing, buffer management, and interop without unsafe code.

What You'll Learn

You will master Span and Memory in C#: creating spans from arrays, strings, and stackalloc memory; slicing spans without allocation; using Memory for heap-storable buffers; stackalloc for stack-allocated buffers; and performance-critical .NET patterns.

Why It Matters

Before Span, slicing arrays or strings allocated new memory. Span provides zero-allocation views, dramatically reducing GC pressure in high-performance code. Span is fundamental to .NET runtime optimizations: UTF-8 processing, JSON Parsing (System.Text.Json), and ASP.NET Core request pipelines all use spans internally.

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 can be stored on the heap:

// 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 is a stack-only type and cannot be used as a field or in async methods. Use Memory for async scenarios and get Span from it.

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 instead.

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 or arrays.

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

  1. What is the difference between Span and Memory?
  2. When would you use stackalloc instead of new T[]?
  3. Why can't Span be used in async methods?
  4. How does slicing with Span avoid memory allocation?
  5. 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 for zero-allocation field extraction. Support quoted fields with escaped quotes. Measure allocations against string.Split.

FAQ

Is Span faster than arrays?

For slicing and manipulation, yes, because it avoids allocation. For creation and indexing, arrays are comparable. Span excels in high-throughput scenarios.

What is the maximum size of a stackalloc buffer?

Stack space is limited (about 1MB per thread). stackalloc buffers larger than a few KB risk StackOverflowException. Use ArrayPool for larger buffers.

Can Span be used as a field in a class?

No. Span is a ref struct and can only appear on the stack. Use Memory for heap-storable references to contiguous memory.

What happens if I slice beyond the end of a Span?

You get an IndexOutOfRangeException at runtime. Always validate span lengths before slicing in production code.

Is ReadOnlySpan truly read-only?

It prevents modification through the span but does not prevent direct modification of the underlying array through other references. It provides read-only access semantics.

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 and Memory in C#. The next lesson covers index and range operators (^, ..) for concise slicing.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro