ref structs

C# 7.2 NETCore 2.1

Published Updated Author Jeffrey T. Fritz Reading time

ref struct types stay on the stack, which makes span-heavy APIs safe and allocation-conscious.

C# 7.2 introduced ref struct to support byref-like types such as Span<T> and ReadOnlySpan<T>. The compiler enforces stack-only rules so these types cannot outlive the memory they point at.

That sounds specialized, but the pattern is practical whenever you want a tiny parser, formatter, or buffer helper to work directly over existing memory without heap allocations.

Why it matters

ref struct is one of the building blocks behind modern high-performance .NET APIs. It lets you bundle together a little bit of behavior and a span-backed view of memory while preserving safety rules that a normal struct cannot express.

If you are reading from buffers, slicing text, or doing short-lived formatting work, it often produces clearer code than passing several spans and indexes around separately.

Cautions

The restrictions are real: a ref struct cannot be boxed, cannot usually be stored in class fields, and cannot cross await or yield suspension points. That means it is for tight synchronous scopes, not long-lived domain objects.

When a plain struct or class is sufficient, prefer the simpler option. Reach for ref struct only when the stack-only rule is the actual safety boundary you need.

Build a small parser over a span

A `ref struct` can safely hold `ReadOnlySpan<T>` state while it walks in-place data without allocating substrings.

Valid since C# 7.2

using System;

public ref struct CsvFieldReader
{
    private ReadOnlySpan<char> _remaining;

    public CsvFieldReader(ReadOnlySpan<char> line)
    {
        _remaining = line;
    }

    public bool TryRead(out ReadOnlySpan<char> field)
    {
        if (_remaining.IsEmpty)
        {
            field = default;
            return false;
        }

        var commaIndex = _remaining.IndexOf(',');
        if (commaIndex < 0)
        {
            field = _remaining;
            _remaining = default;
            return true;
        }

        field = _remaining.Slice(0, commaIndex);
        _remaining = _remaining.Slice(commaIndex + 1);
        return true;
    }
}

public static class Program
{
    public static void Main()
    {
        CsvFieldReader reader = new CsvFieldReader("Ada Lovelace,Mathematician,London".AsSpan());

        ReadOnlySpan<char> field;
        while (reader.TryRead(out field))
        {
            Console.WriteLine(field.ToString());
        }
    }
}

Wrap stackalloc memory in a focused helper

`ref struct` helpers are a natural way to keep stack-only buffers together with the operations that use them.

Valid since C# 7.2

using System;

public ref struct AsciiScratchBuffer
{
    private Span<byte> _buffer;

    public AsciiScratchBuffer(Span<byte> buffer)
    {
        _buffer = buffer;
    }

    public int WriteUppercase(ReadOnlySpan<char> text)
    {
        var written = 0;

        foreach (var character in text)
        {
            _buffer[written++] = (byte)char.ToUpperInvariant(character);
        }

        return written;
    }
}

public static class Program
{
    public static void Main()
    {
        Span<byte> bytes = stackalloc byte[32];
        AsciiScratchBuffer scratch = new AsciiScratchBuffer(bytes);
        int length = scratch.WriteUppercase("ok-42".AsSpan());

        for (int index = 0; index < length; index++)
        {
            Console.Write((char)bytes[index]);
        }
    }
}

Learn more

ref structure types (Microsoft Learn)

Newer capabilities

  1. ref fields inside ref structs

    Introduced in C# 11.0

    C# 11 allows ref fields inside a ref struct, which is useful for advanced buffer wrappers and iterator-like helpers that need to hold on to an existing storage location directly.