ref struct 是栈专属内存契约,绑定栈生命周期,不可用于类字段、泛型集合、async局部变量或闭包捕获;可传参、ref返回及C#13起带where T:ref struct约束的泛型中使用。

ref struct 不能当普通 struct 用,编译器会直接报错 —— 它不是“加强版 struct”,而是“栈专属内存契约”。
ref struct 声明和基本限制
必须显式写 ref struct,不能只写 struct 再加修饰;一旦声明,它就绑定栈生命周期。编译器会在多个层面拦截越界使用:
- 放在
class字段里 → 报CS8342(A ref struct cannot be used as a field) - 放进
List、T[]或任何泛型集合 → 编译失败(泛型类型参数默认不允许ref struct) - 作为
async方法的局部变量(C# 13 之前)→ 报CS8350(This combination of arguments to an async method is not supported) - 在 lambda 或本地函数中捕获 → 编译器拒绝,因为可能逃逸到堆
典型合法声明:
public ref struct SpanWrapper
{
public ReadOnlySpan<byte> Data;
public int Offset;
}</byte>
ref struct 中的 ref 字段怎么写
ref 字段只能出现在 ref struct 里,表示该字段本身存储的是一个引用(即地址),而不是值拷贝。它不自动初始化,必须手动绑定有效内存:
- 声明语法是
privateref int number;,不是private ref int number;(空格非法) - 必须在构造函数或初始化逻辑中用
ref表达式赋值,例如number = ref localVariable; - 读取前建议用
Unsafe.IsNullRef(ref number)检查是否为空引用,否则可能触发NullReferenceException
示例:
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;
}
ref struct 能否传参、返回、存入泛型容器
能,但有明确前提:调用链全程保持栈安全,且泛型约束显式允许。
- 可作为方法参数传入(按值传递,本质是复制栈地址)
- 可被
ref返回(public ref Span),但返回的必须是当前栈帧内有效的内存GetSpan() => ref _span; - 从 C# 13 开始,可作泛型类型参数,但必须带
where T : ref struct约束,例如:public class Poolwhere T : ref struct { ... } - 仍不能放进
Task的Result,因为Task是堆对象,T会尝试装箱或逃逸
什么时候该用 ref struct,什么时候不该用
核心判断标准不是“想快”,而是“能否接受生命周期完全由当前栈帧控制”。
- 适合:短期切片操作(如解析网络包头)、
stackalloc后的临时封装、高性能序列化中间结构 - 不适合:需要跨 await、跨 yield return、要存进缓存字典、要作为事件参数广播、或任何可能被长期持有的场景
- 容易忽略的坑:
Span和ReadOnlySpan本身就是ref struct,所以所有对它们的赋值、字段存储、泛型传递都受同样限制 —— 不是你的代码错,是设计契约使然
真正难的从来不是写法,而是判断某段逻辑是否真的属于“热路径中的零开销切片”,而不是误把调试便利性当性能优化。











