ref struct 必须显式声明为 ref struct,不可继承、不可泛型容器存储、不可逃逸栈帧,ref 字段需手动绑定有效地址,仅支持 Span 等栈安全类型。

ref struct 声明必须带 ref 关键字,不能省略或后置
编译器不接受 struct MyRefStruct 这种写法——哪怕你只在内部用 ref 字段也不行。必须显式写成 ref struct MyRefStruct,否则直接报错 CS8342(A ref struct cannot be used as a type argument)或类似提示。
常见误操作:
- 把已有普通
struct加上ref修饰 → 编译失败,不是语法糖,是全新类型类别 - 试图继承
ref struct→ 不允许,连: IDisposable都不能加 - 在泛型约束外使用它作类型参数,比如
List→ 编译失败,泛型容器默认拒绝ref struct
ref struct 不能逃逸出当前栈帧,async/lambda/yield 是高危区
它的生命周期被硬绑定到声明它的那个栈帧。一旦可能跨栈(比如 await 暂停后恢复、yield 返回状态机、lambda 捕获后存入委托),编译器立刻拦截。
典型错误场景:
-
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不离开该循环体,就合法
关键判断点:看变量是否可能被“带走”——返回、存字段、传给异步/委托/迭代器,都是禁止动作。
ref 字段只能出现在 ref struct 内部,且必须手动绑定有效地址
ref 字段不是自动初始化的引用,它本质是一个栈地址存储槽,必须在构造逻辑中用 ref 表达式显式赋值。
正确写法示例:
public ref struct RefFieldExample
{
private ref int _value;
<pre class="brush:php;toolbar:false;">public RefFieldExample(ref int source)
{
_value = ref source;
}
public int GetValue() => _value;
}
注意点:
- 声明是
private ref int _value;,不是private ref int(空格非法) - 未初始化的
ref字段读取会触发NullReferenceException,建议用Unsafe.IsNullRef(ref _value)检查 - 不能指向局部变量以外的内存(比如方法返回的临时值),否则编译器会报“ref safety violation”
C# 13 起支持 where T : ref struct,但泛型容器仍受限
从 C# 13 开始,ref struct 可以作为泛型类型参数,但必须加显式约束:
public class Pool<t> where T : ref struct
{
private T[] _buffer; // ❌ 仍然不行:数组本身是引用类型,元素不能是 ref struct
private Span<t> _span; // ✅ 合法:Span 支持 ref struct 元素
}</t></t>
能做的:
- 作为方法参数传入(按值传递,实际复制的是栈地址)
- 被
ref返回(public ref Span)GetData() => ref _data; - 用于
Span、ReadOnlySpan等原生支持栈安全的类型
不能做的:
- 放进
T[]、List、Task—— 这些都隐含堆分配或装箱风险 - 作为
object或接口类型接收,哪怕只是临时转一下 → 编译器直接拦住
真正难的不是“怎么写”,而是“怎么保证整条调用链都不让它逃逸”。哪怕一个看似无害的 ToString() 调用,如果背后隐式用了 object 参数,也会崩。栈安全是全程契约,不是单点开关。











