ref 结构中的 ref 字段赋值规则

问题描述 投票:0回答:2

经过几个小时的谷歌搜索,我仍然找不到答案。每个人都跳过那一点。

所以我有一个像这样的引用结构

ref struct RefStruct
{
    public ref int X;
}

我尝试这样使用它

void f()
{
    var x = 100;
    var s = new RefStruct();
    s.X = ref x;
}

这给了我一个编译器错误:无法将“ref x”引用分配给“s.X”,因为“ref x”的转义范围比“s.X”更窄

这到底是什么意思?它们都在同一范围内。

让我们添加一个构造函数

ref struct RefStruct
{
    public ref int X;
    
    public RefStruct(ref int x)
    {
        X = ref x;
    }
}

void f()
{
    var x = 100;
    var s = new RefStruct(ref x);
    s.X = ref x;
}

现在绝对没问题了,

s
范围变窄了……但是改变了什么?
x
的范围是如何扩大的?

事情可能更奇怪

void f()
{
    var x = 100;
    var y = 5;
    var s = new RefStruct(ref y);
    s.X = ref x;
}

这个也可以。

我真的不明白为什么我必须通过构造函数初始化

ref
字段,直到我能够重新分配它。

c# struct ref
2个回答
2
投票

阅读介绍ref字段的

语言提案

局部变量的默认“转义范围”基于其初始化方式。确切的语义有点奇怪,是围绕 C#10 中引入的语义设计的,以支持返回

Span<T>
的实例。

// safe-to-escape == calling method
Span<int> x = default;
return x; // ok

// safe-to-escape == current method
Span<int> y = stackalloc int[42];
return y; // error

从您的示例来看,即使您没有返回任何

ref struct
变量。编译器正在强制执行“转义范围”规则,就好像您将返回它们一样。

// safe-to-escape == calling method, can be returned
var s = new RefStruct();
// safe-to-escape == current method, can't be returned
var s = new RefStruct(ref x);

当您尝试分配

s.X = ref x
时。左侧将具有上面变量的“转义范围”,但
ref x
将具有“当前方法”的范围。

如果您尝试将“当前方法”作用域分配给“调用方法”作用域,则会引发 CS8374 错误。

但是您可以通过向结构变量添加显式

scoped
修饰符来覆盖默认的转义范围定义;

// safe-to-escape == current method
scoped var s = new RefStruct();

换句话说,编译器根据局部变量的初始化方式向它们添加隐式

scoped
关键字。但您始终可以显式添加关键字。

在撰写本文时,有一个 C# 编译器 issue 来改进 CS8374 错误消息,建议在相关局部变量上使用

scoped
修饰符。


0
投票

无法将“ref x”引用分配给“s.X”,因为“ref x”的转义范围比“s.X”窄

这里你应该明白的是,

s
的范围更广,因为它是
var
而不是
ref var

很明显为什么

ref x
的范围比
var s
窄,它是一个局部变量,
s
可能比
x
更长寿。

文档说:

编译器确保存储在 ref 字段中的引用不会比其所指对象的寿命更长。

基本上,您发布的最后一个片段对于编译器来说是启发式有效的。

为什么在构造函数中初始化然后赋值有效?这是因为

X
的存储被初始化了。

尝试调试这个编译后的例子来理解,它会在运行时NRE:

var x = 100;
var s = new RefStruct();
s.X = x; // no 'ref' keyword here
© www.soinside.com 2019 - 2024. All rights reserved.