ref struct 是编译器强制绑定栈帧的内存契约,必须显式声明为 ref struct,禁止继承、装箱、泛型容器存储、类字段、async/lambda/yield 捕获及接口实现,仅限当前栈帧内安全使用。

ref struct 不是“能放栈上的 struct”,它是编译器强制绑定到当前栈帧的内存契约——一旦声明,它就拒绝一切可能逃逸的操作,包括 async、lambda、泛型容器、类字段等。用错不是性能差,而是直接编译失败。
ref struct 声明必须带 ref 关键字,不能省略或后置
编译器不接受 struct MyRefStruct 这种写法,哪怕内部只含 ReadOnlySpan<byte></byte> 或一个 ref int 字段也不行。必须显式写成 ref struct MyRefStruct,否则报 CS8342(A ref struct cannot be used as a type argument)或类似错误。
- 已有普通
struct直接加ref修饰 → 编译失败,这不是语法糖,是全新类型类别 -
ref struct不能继承任何类型,连IDisposable都不能实现(接口实现会隐式允许装箱/堆引用) - 命名时别用
Ref当后缀误导自己,比如MyStructRef—— 它不是“带 ref 的 struct”,而是“ref struct”
ref struct 中的 ref 字段必须手动绑定有效地址
ref 字段不是自动初始化的引用,它本质是一个栈地址槽,未赋值就读取会触发 NullReferenceException;且只能绑定到当前栈帧内确定存活的变量。
- 声明语法是
private ref int _value;,private ref int(空格非法)会编译报错 - 必须在构造函数中用
ref表达式显式赋值:_value = ref source; - 读取前建议用
Unsafe.IsNullRef(ref _value)检查,尤其在复杂初始化路径中 - 不能绑定到方法返回的临时值,例如
_value = ref GetLocalInt();→ 编译器报 “ref safety violation”
ref struct 不能逃逸出当前栈帧,async/lambda/yield 是高危区
它的生命周期硬绑定到声明它的那个栈帧。只要变量可能被“带走”,编译器立刻拦截——返回、存字段、传给异步/委托/迭代器,全都不行。
-
async Task Foo() { var x = new MyRefStruct(); await Task.Delay(1); }→ 报CS8350 -
Task.Run(() => { var x = new MyRefStruct(); })→ lambda 捕获导致逃逸,编译失败 -
foreach (var item in list) { ref struct s = ...; DoSomething(s); }→ 只要s不离开该循环体,就合法 -
public ref Span<int> GetSpan() => ref _span;</int>→ 合法,但返回的必须是当前栈帧内有效的内存(如局部stackalloc或参数传入的ref)
C# 13 起支持 where T : ref struct 泛型约束
ref struct 终于可以进泛型了,但前提是显式约束,且调用链全程保持栈安全。它仍不能进 Task<t></t>、List<t></t> 或任何默认泛型容器。
- 正确用法:
public class Pool<t> where T : ref struct { ... }</t> - 错误用法:
List<myrefstruct></myrefstruct>→ 编译失败,泛型定义没加约束,且List本身是堆类型 - 不能作为
class字段:public MyRefStruct Field;→ 报CS8342(A ref struct cannot be used as a field) - 不能装箱:
object o = new MyRefStruct();→ 编译拒绝,无运行时妥协空间
真正难的不是写对语法,而是判断“这个数据是否真的适合栈上分配”:它是否生命周期严格限定在当前同步方法内?是否会被异步暂停、闭包捕获、状态机保存、或跨方法返回?只要有一个“可能”,就得换方案——比如改用 Memory<byte></byte> + ArrayPool<byte>.Shared</byte>。










