in 和 ref 只读参数之间的区别

Sim*_*ant 12 c# ref c#-12.0

in和参数有什么区别ref readonly

我发现inref readonly参数都使参数只读,并且不能在被调用的方法中修改。它们有类似的功能吗?

小智 8

想象一下,您需要对值的引用而不是值作为方法的参数,但您struct要传递的是readonly struct. 截至目前,您有三个选项可以通过引用传递:

  1. 参数ref:这不是您的选择,因为您传递的是readonly struct无法修改的参数,但由于您可以在技术上修改ref参数,因此编译器必须生成 的副本readonly struct以保证readonly struct不被修改。

  2. 参数in:这对于您的情况不适用,因为虽然它避免复制整个struct,并禁止修改,但不能保证您的参数实际上将通过引用传递 - 您可以in在传递参数时省略关键字,以传递它按价值。这意味着,如果您需要引用不安全的方法,那么这是行不通的。使用 in 关键字的原因不是为了通过只读引用传递参数,而是为了避免分配。这通常意味着通过引用传递参数,但这不是必需的。

  3. readonly ref参数解决了上述问题:编译器警告保证该值必须通过引用传递,并且不会有保护副本,因为您无法修改该参数。

结论:ref parameter如果您需要引用readonly struct. 该in参数不保证该参数是通过引用传递的,并且该ref参数允许修改,这意味着编译器必须复制该对象以确保它不被修改。


Fre*_*eft 6

主要区别在于调用方。假设您有一个相当大的结构,您绝对不想复制它:

unsafe struct Foo
{
    // alternatively imagine a lot of fields being in here
    public fixed int Bar[32];
}
Run Code Online (Sandbox Code Playgroud)

in如果您声明一个带有如下参数的方法:

public unsafe int SumOverFoo(in Foo foo)
{
    foo.Bar[0] = 42; // not allowed
    int sum = 0;
    for (int i = 0; i < foo.Bar.Length; i++)
    {
        sum += foo.Bar[i];
    }
    return sum;
}
Run Code Online (Sandbox Code Playgroud)

没有什么可以阻止您(意外地)调用如下方法:

Foo foo = default;
int sum = SumOverFoo(foo); // yikes, you just copied 128 bytes around for no reason
Run Code Online (Sandbox Code Playgroud)

将参数更改foo为 beref readonly将生成编译器警告,您应该foo通过引用传递(使用inref)。

public unsafe int SumOverFoo(ref readonly Foo foo)
{
    foo.Bar[0] = 42; // still not allowed
    int sum = 0;
    for (int i = 0; i < foo.Bar.Length; i++)
    {
        sum += foo.Bar[i];
    }
    return sum;
}
Run Code Online (Sandbox Code Playgroud)
Foo foo = default;
int sum = SumOverFoo(foo); // CS9192: Argument 1 should be passed With 'ref or 'in' keyword
Run Code Online (Sandbox Code Playgroud)

ref readonly引入参数的方式还可以防止在将in参数存在之前引入的旧 API(因此使用ref)迁移到现在接受只读参数时发生重大更改。例如,.NET 8/C#12 之前的版本Volatile.Read(...)需要 ref 参数(尽管它不需要写入权限),这意味着无法使用 .NET 读取readonly字段Volatile.Read()。他们也不能只是将其更改为in,因为这会破坏使用Volatile.Read(ref _myField). in此外,在意外忘记使用关键字并在使用时创建意外副本后可能会执行非易失性、非原子读取,这Volatile.Read()将是一个非常令人讨厌的错误来源。

因此,使用 .NET 8 更新这些旧的 API 以支持更广泛的用例是引入参数的原因之一ref readonly(因为它们与传递in 或 一起 ref工作)。

有关更多详细信息,请参阅有关 ref readonly 参数的 C# 语言设计规范。

  • "int sum = SumOverFoo(foo); // 哎呀,你刚刚复制了 128 个字节..." 只有当你也有一个没有“in”修饰符的重载时才会发生这种情况,对吗? (3认同)