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 struct
types
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:
-
ref
locals orref struct
variables inasync
methods -
ref
locals orref struct
variables in iterator methods -
unsafe
code 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
ref
locals andref struct
variables inasync
methods - ✅ You can use
unsafe
code in iterator methods - ⚠️ But you cannot access those
ref
locals across anawait
oryield 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:
-
await
andyield
break 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.