You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于将readonly struct作为in参数传递的微软编码建议的疑问

关于C# in参数与readonly struct的疑问解答

这确实是C#里in参数和结构体不可变性容易绕晕的点,我来一步步拆解清楚:

为什么readonly struct能帮in参数避免防御性副本?

首先得明确:in参数是只读引用传递,但这个“只读”是编译器帮你做的语法检查——它禁止你直接给in参数赋值,但管不住结构体内部的实例方法会不会偷偷修改字段。

比如你定义了一个非readonly的结构体,里面有个普通实例方法,这个方法本身是可以修改结构体的字段的(因为值类型的实例方法默认有权限修改自身字段)。这时候编译器犯难了:如果我直接用in引用的原结构体去调用这个方法,万一方法修改了字段,就违反了in参数的只读约定。为了保证安全,编译器只能生成一个防御性副本:把原结构体复制一份,在副本上调用方法,这样原结构体不会被修改,但额外的复制操作就产生了。

而readonly struct就不一样了:编译器会强制这个结构体的所有实例成员都是readonly的(包括自动属性的setter会被禁用,实例方法不能修改字段)。这意味着,任何针对readonly struct的成员调用,都不可能修改它的内部状态。编译器可以100%放心,直接用in引用的原结构体就行,完全不需要生成防御性副本,自然就没了额外的性能开销。

举个直观的代码例子:

// 非readonly可变结构体
struct MutablePoint
{
    public int X;
    public int Y;
    // 这个方法会修改结构体字段
    public void Move(int dx, int dy)
    {
        X += dx;
        Y += dy;
    }
}

void ProcessPoint(in MutablePoint p)
{
    p.Move(1, 1); 
    // 编译器会偷偷生成p的副本,在副本上调用Move,原p完全没变化
}

// readonly不可变结构体
readonly struct ImmutablePoint
{
    public int X { get; }
    public int Y { get; }
    public ImmutablePoint(int x, int y) => (X, Y) = (x, y);
    // 不修改原结构体,返回新实例
    public ImmutablePoint Move(int dx, int dy) 
        => new ImmutablePoint(X + dx, Y + dy);
}

void ProcessImmutablePoint(in ImmutablePoint p)
{
    var newPoint = p.Move(1, 1);
    // 编译器直接用原p的引用,无副本生成
}

非readonly结构体作为in参数,为啥会有性能和行为问题?

性能负面影响

刚才说的防御性副本是核心问题:如果结构体体积较大(比如包含多个数组、大字段),或者你频繁调用它的非readonly成员,每次调用都会产生一次复制,这个开销会比直接按值传递结构体还大——毕竟in参数本来是为了减少复制才设计的,结果反而弄巧成拙。

难以排查的行为问题

更坑的是逻辑不符预期:你以为in参数是引用,操作的是原对象,但实际上操作的是编译器生成的副本,原对象根本没变化。比如这个例子:

struct MutableCounter
{
    public int Count;
    public void Increment() => Count++;
}

void UpdateCounter(in MutableCounter counter)
{
    counter.Increment(); 
    // 这里操作的是副本,原counter的Count完全没被修改!
}

// 调用代码
var c = new MutableCounter();
UpdateCounter(in c);
Console.WriteLine(c.Count); // 输出0,而不是你预期的1

这种问题非常隐蔽,代码看起来完全合理,但运行结果就是不对,排查起来特别费劲——因为你很难想到编译器偷偷生成了副本。

总结一下

in参数的“只读”是语法层面的限制,但对于可变结构体,编译器无法保证其成员方法不破坏只读约定,只能通过防御性副本妥协;而readonly struct从类型层面强制了不可变性,让编译器可以安全地跳过副本,既保证性能,又避免了诡异的行为。所以微软才会建议:只有当结构体是readonly,或者你只调用它的readonly成员时,才用in参数传递结构体。

内容的提问来源于stack exchange,提问作者Firld

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 08:34:09