跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

C#中ref struct的用法_C# ref结构体栈分配教程【精通】

ref struct 是编译器强制绑定栈帧的内存契约,必须显式声明为 ref struct,禁止继承、装箱、泛型容器存储、类字段、async/lambda/yield 捕获及接口实现,仅限当前栈帧内安全使用。 ref struct 不是“能放栈上的 struct”,它是编译器强制绑定到当前栈帧的内存契约——一旦声明,它就拒绝一切可能逃逸的操作,包括 async、lambda、泛型容器、类字段等。用错不是性能差,而是直接编译失败。 ref struct 声明必须带 ref 关键字,不能省略或后置 编译器不接受
struct MyRefStruct
这种写法,哪怕内部只含
ReadOnlySpan
或一个
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 是高危区 它的生命周期硬绑定到声明它的那个栈帧。只要变量可能被“带走”,编译器立刻拦截——返回、存字段、传给异步/委托/迭代器,全都不行。 C知道 CSDN推出的一款AI技术问答工具 下载
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 GetSpan() => ref _span;
→ 合法,但返回的必须是当前栈帧内有效的内存(如局部
stackalloc
或参数传入的
ref
) C# 13 起支持
where T : ref struct
泛型约束 ref struct 终于可以进泛型了,但前提是显式约束,且调用链全程保持栈安全。它仍不能进
Task
、
List
或任何默认泛型容器。 正确用法:
public class Pool where T : ref struct { ... }
错误用法:
List
→ 编译失败,泛型定义没加约束,且
List<>
本身是堆类型 不能作为
class
字段:
public MyRefStruct Field;
→ 报
CS8342
(A ref struct cannot be used as a field) 不能装箱:
object o = new MyRefStruct();
→ 编译拒绝,无运行时妥协空间 真正难的不是写对语法,而是判断“这个数据是否真的适合栈上分配”:它是否生命周期严格限定在当前同步方法内?是否会被异步暂停、闭包捕获、状态机保存、或跨方法返回?只要有一个“可能”,就得换方案——比如改用
Memory
+
ArrayPool.Shared
。

相关文章