ref and unsafe in Async and Iterator Methods — Unlocking Span<T> in C# 13
Starting in C# 13, the language lifts one of its long-standing restrictions: the inability to use ref struct types, ref variables, or unsafe contexts in iterator (yield) or async methods.
This evolution is critical for developers working with performance-sensitive data, particularly through types like:
System.Span<T>System.ReadOnlySpan<T>- Custom
ref structtypes
Let’s dive deep into what this change allows, what restrictions remain, and how it helps you write better, faster, and safer C# code.
Old Limitation: No ref in async or yield
Before C# 13, you couldn’t declare or use:
-
reflocals orref structvariables inasyncmethods -
reflocals orref structvariables in iterator methods -
unsafecode blocks inside iterator methods
This made it impossible to use Span<T>, a high-performance, stack-only data structure, in any method that returned a Task, or used yield return.
What’s New in C# 13?
C# 13 partially lifts these restrictions:
- ✅ You can declare
reflocals andref structvariables inasyncmethods - ✅ You can use
unsafecode in iterator methods - ⚠️ But you cannot access those
reflocals across anawaitoryield return
This strikes a balance between safety and power. The compiler enforces boundaries to ensure memory safety.
Example: Using Span<T> in Async Method
public async Task ProcessAsync()
{
ReadOnlySpan<byte> span = stackalloc byte[10];
Console.WriteLine(span[0]);
await Task.Delay(100); // 🔴 Can't access `span` after this
}
✅ Compiles successfully
❌ But if you access span after await, the compiler produces an error.
Why These Restrictions?
ref struct types like Span<T> are stack-only. But:
-
awaitandyieldbreak execution flow - They might resume later — on a different stack
- That would make references to previous stack frames unsafe
So C# enforces:
| Context | Rule |
|---|---|
async |
Can't use ref vars after await
|
iterator (yield) |
Can't use ref vars after yield
|
unsafe in iterator |
Allowed — but yield must be safe |
Compiler Checks You Can Rely On
You’ll get CS4000-level compile-time errors if you violate the boundaries.
ref struct MyStruct { public int X; }
async Task Invalid()
{
var val = new MyStruct();
await Task.Yield();
Console.WriteLine(val.X); // ❌ CS4015: Can't access ref struct here
}
The compiler protects you from undefined behavior.
Best Use Cases
| Scenario | Benefit |
|---|---|
Parsing binary data in async IO |
Use Span<T> for performance and safety |
| High-performance pipelines | Write modern iterator-like APIs with stack safety |
| Unsafe algorithms in iterators | Run efficient logic while yielding intermediate values |
Custom low-level ref struct
|
Unlocks new use cases with async-compatible parsing |
Learn More
Final Thoughts
C# 13 opens the door for more safe, high-performance patterns, especially when working with memory-efficient types like Span<T>. By relaxing restrictions while preserving compiler-enforced safety, it empowers advanced developers to optimize data pipelines and I/O-heavy logic without sacrificing safety or structure.
Unlock the power of ref and Span<T> — even in your asynchronous workflows.
Written by: [Cristian Sifuentes] – Low-Level C# Engineer | Span Evangelist | .NET Runtime Tuner
Have you tried using Span<T> in async or iterator methods? Share your insights.

Top comments (0)