<p>ref readonly 是 C# 7.2 引入的底层只读引用机制,通过 IL 的 readonly. 前缀禁止修改所指内存,编译器阻止赋值和调用非 readonly 方法,但不阻止读取或复制后修改。</p>

ref readonly 是什么,它真能阻止修改吗
ref readonly 不是“只读引用”的语法糖,而是 C# 7.2 引入的底层机制:它让编译器在 IL 层面生成 ldarg.0(或类似)后跟 readonly. 前缀指令,向 JIT 表明该引用所指向的内存不可被写入。这意味着:
- 编译器会禁止你对
ref readonly变量本身赋值(比如r = ref other;报错) - 更关键的是,禁止通过它调用任何可能修改字段的方法(包括
set访问器、非readonly实例方法) - 但它不阻止你读取字段、调用
readonly方法、或把值复制出来再改
常见错误现象:ref readonly Person p = ref GetPerson(); p.Name = "new"; —— 直接编译失败,提示 “Cannot assign to variable ‘p’ because it is a readonly variable”
什么时候必须用 ref readonly 而不是普通 readonly 或 in 参数
ref readonly 的核心价值在于:零拷贝 + 只读语义 + 支持任意结构体(包括未标记 readonly struct 的)
- 普通
readonly字段或局部变量只是“不可重新赋值”,但若类型是可变结构体(如Span<byte>),它内部字段仍可被改 -
in参数本质是编译器对ref readonly的语法糖,仅用于参数传递;无法用于返回、字段、局部变量等场景 - 如果你要从一个大结构体数组中“借出”一个元素供只读访问(比如
ref readonly BigStruct[] arr中取ref readonly arr[1000]),不用ref readonly就得复制整个结构体(可能几百字节)
使用场景举例:
- 高性能数值计算中避免
Vector4复制 -
ReadOnlySpan<T>内部实现中安全暴露底层数据引用 - 自定义集合的
GetRefReadonly(int index)方法
ref readonly 返回值的陷阱和限制
C# 不允许直接返回局部变量的 ref readonly,因为栈内存会销毁。但容易忽略的是:它也不允许返回临时表达式的引用。
以下代码全部非法:
-
return ref readonly new Vector3(1, 2, 3);(临时对象) -
return ref readonly _items[index];(如果_items是List<T>,其this[]不是ref索引器) -
return ref readonly GetSomeStruct().Field;(返回临时结构体的字段引用)
必须满足:
- 返回源必须有明确生命周期(通常是类字段、数组元素、或传入的
ref readonly参数) - 对应的索引器或属性必须声明为
ref readonly(C# 7.2+) - 若返回
struct字段,该struct最好也标记为readonly struct,否则编译器可能因“可能触发隐式复制”而报错
ref readonly 和 readonly struct 的配合要点
readonly struct 是给编译器的“承诺”:这个结构体所有实例方法都不会修改字段。但它是可选的;而 ref readonly 是强制的访问约束。
两者配合时关键点:
- 如果结构体没标
readonly struct,但你用ref readonly引用它,调用其非readonly方法仍会报错(如r.SomeMethod()→ “Cannot call method because it’s not marked readonly”) -
ref readonly允许你安全读取字段,哪怕结构体本身可变;但一旦想调用方法,就必须依赖readonly标记来过编译 - 性能上:未标记
readonly struct的类型,在ref readonly上调用readonly方法时,JIT 仍可能插入防御性复制(尤其涉及装箱或泛型约束时)
最容易被忽略的地方:ref readonly 的安全性完全由编译器静态检查保障,运行时不产生额外开销,但一旦绕过(比如用 unsafe 或反射),它就形同虚设。别把它当成运行时防护机制。


















