C# ref readonly参数方法 C#如何传递大的struct而无需拷贝

来源:这里教程网 时间:2026-02-21 17:40:40 作者:

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
带来的收益可能被可维护性成本抵消。实际压测比理论推导更可靠。

相关推荐