编译器无法确定该Offset
方法会改变Point
结构成员。但是,readonly
与非只读字段相比,结构字段的处理方式不同。考虑这个(非只读)结构:
public struct TwoPoints {
private readonly Point one;
private Point two;
public void Foo() {
one.Offset(5, 5);
Console.WriteLine(one.X); // 0
two.Offset(5, 5);
Console.WriteLine(two.X); // 5
}
}
one
字段是只读的,但two
不是。readonly
现在,当您在struct 字段上调用方法时- struct 的副本将作为this
. 如果方法改变了结构成员——那么这个副本成员就会改变。出于这个原因,您不会观察到one
after 方法被调用的任何变化——它没有被改变,但副本被改变了。
two
字段不是只读的,结构本身(不是副本)被传递给Offset
方法,因此如果方法改变成员 - 您可以观察到它们在方法调用后发生变化。
因此,这是允许的,因为它不能破坏您的readonly struct
. readonly struct
应该是的所有字段readonly
,并且在只读结构字段上调用的方法不能改变它,它们只能改变一个副本。
如果您对此的“官方来源”感兴趣 - 规范(7.6.4 会员访问)说:
如果 T 是结构类型并且 I 标识了该结构类型的实例字段:
• 如果 E 是一个值,或者如果该字段是只读的并且引用出现在声明该字段的结构的实例构造函数之外,则结果是一个值,即给定结构实例中字段 I 的值再见。
• 否则,结果是一个变量,即 E 给定的结构实例中的字段 I。
T
这是目标类型,并且I
是正在访问的成员。
第一部分说,如果我们在构造函数之外访问结构的实例只读字段,结果是value。在第二种情况下,实例字段不是只读的 - 结果是变量。
然后“7.5.5 函数成员调用”部分说(E
这里是实例表达式,例如one
和two
上面):
• 如果 M 是在值类型中声明的实例函数成员:
如果 E 未被分类为变量,则创建 E 类型的临时局部变量,并将 E 的值分配给该变量。然后将 E 重新分类为对该临时局部变量的引用。临时变量可以在 M 中作为 this 访问,但不能以任何其他方式访问。因此,只有当 E 是一个真正的变量时,调用者才有可能观察到 M 对此所做的更改。
正如我们在上面看到的,只读结构字段访问导致 a value
,而不是变量,因此E
不被归类为变量。