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]);
}
}
}
Related features
Learn more
Newer capabilities
-
ref fields inside ref structs
Introduced in C# 11.0
C# 11 allows
reffields inside aref struct, which is useful for advanced buffer wrappers and iterator-like helpers that need to hold on to an existing storage location directly.