ref readonly参数能避免struct拷贝吗
能,但仅限于方法体内不修改该参数的前提下。C# 7.2 引入的
ref readonly允许你以只读引用方式传递
struct,彻底跳过值类型的默认按值复制行为——前提是调用方传入的是可寻址的左值(比如局部变量、数组元素、字段),而非临时对象(如字面量或 new 表达式结果)。
常见误区:以为加了
ref readonly就“一定不拷贝”。其实如果传入的是不可寻址的右值(例如
SomeMethod(ref readonly new BigStruct())),编译器会报错
CS8337: Cannot use a result of 'new BigStruct()' as a ref or out value because it is not a variable,根本过不了编译。
什么时候必须用ref readonly而不是ref
当你需要高性能访问大型
struct(比如含多个
double字段的几何类型、固定大小缓冲区等),又**明确禁止方法内部修改其状态**时,
ref readonly是唯一兼顾安全与零拷贝的选择。
ref虽然也避免拷贝,但开放了写权限,容易破坏封装或引发意外副作用;而
readonly修饰后,编译器会在方法体内对所有成员访问做只读检查: 不能给字段赋值(
param.x = 1;→ 编译错误) 不能调用非
readonly成员方法(哪怕该方法逻辑上不修改状态) 可以安全地读取字段、调用
readonly方法、访问属性(只要 getter 是
readonly)
示例:
struct Matrix4x4
{
public double M11, M12, M13, M14;
// ... 16个double,约128字节
public readonly double Determinant => /* 计算逻辑 */;
}
<p>void ProcessMatrix(ref readonly Matrix4x4 m)
{
Console.WriteLine(m.Determinant); // ✅ OK
// m.M11 = 0; // ❌ 编译错误
// m.ToString(); // ❌ 若ToString()不是readonly方法
}
ref readonly参数的调用限制和陷阱
它对调用端有严格要求,稍不注意就触发编译错误或隐式拷贝:
只能传入变量、字段、数组索引等“可寻址位置”,不能传表达式结果(ProcessMatrix(ref readonly GetMatrix())❌) 不能用于
async方法参数(因为 await 可能导致栈帧移动,引用失效) 不能作为
out或
ref参数重载的区分依据(
void M(ref T)和
void M(ref readonly T)不能共存) 若 struct 含引用类型字段(如
string),
ref readonly只保证 struct 本身地址不变、字段不可改,但不阻止通过引用字段间接修改堆对象
性能提示:对于小于 16 字节的小 struct(如
Point,
Guid),按值传递反而可能更快——CPU 寄存器能直接承载,避免取地址和解引用开销。
替代方案对比:Span 和 in 参数
C# 7.2 同时引入了
in参数关键字,语义上等价于
ref readonly,但更简洁且意图更明确:
void M(in Matrix4x4 m)等价于
void M(ref readonly Matrix4x4 m)
in更推荐用于只读输入场景,编译器对其做了额外优化(如允许传入只读临时变量,某些情况下放宽右值限制) 若需切片或遍历 struct 的原始字节(比如序列化),
Span<byte></byte>配合
MemoryMarshal.AsBytes更底层可控,但需
unsafe或
System.Runtime.CompilerServices.Unsafe
真正复杂的地方在于:是否值得为避免一次拷贝,增加调用约束、破坏 API 易用性?尤其当 struct 生命周期短、调用频次低时,
in带来的收益可能被可维护性成本抵消。实际压测比理论推导更可靠。
