为什么 Point.Offset() 不在只读结构中给出编译器错误?

Why is Point.Offset() not giving a compiler error in a readonly struct?

也许我误解了只读结构的概念,但我认为这段代码不应该编译:

public readonly struct TwoPoints
{
    private readonly Point one;
    private readonly Point two;

    void Foo()
    {
        // compiler error:  Error CS1648  Members of readonly field 'TwoPoints.one'
        // cannot be modified (except in a constructor or a variable initializer)
        one.X = 5;

        //no compiler error! (and one is not changed)
        one.Offset(5, 5);
    }
 }

(我使用的是 C# 7.3。) 我错过了什么吗?

支持, there is an article in regards the Point's method: Point.Offset()

的回答

Note that calling the Offset method will only have an effect if you can change the X and Y properties directly. Because Point is a value type, if you reference a Point object by using a property or indexer, you get a copy of the object, not a reference to the object. If you attempt to change X or Y on a property or indexer reference, a compiler error occurs. Similarly, calling Offset on the property or indexer will not change the underlying object. If you want to change the value of a Point that is referenced as a property or indexer, create a new Point, modify its fields, and then assign the Point back to the property or indexer.


根据定义 Point 声明为 structPoint

最后但同样重要的是,微软的博客上有an article,其中描述readonly fields,实际上是struct,它们的public属性是只读的还有。

A read-only struct is a struct whose public members are read-only, as well as the “this” parameter.

编译器无法确定 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 结构字段上调用方法时,结构的 copy 将作为 this 传递给该方法。如果方法改变了结构成员——那么这个副本成员就会改变。出于这个原因,您在调用方法后没有观察到 one 的任何更改 - 它没有更改,但复制了。

two 字段不是只读的,并且结构本身(不是副本)被传递给 Offset 方法,因此如果方法改变了成员——你可以观察到它们在方法调用后发生了变化。

因此,这是允许的,因为它不会破坏您 readonly struct 的不变性契约。 readonly struct的所有字段都应该是readonly,在readonly struct field上调用的方法不能改变它,它们只能改变一个副本。

如果您对此感兴趣 "official source" - 规范(7.6.4 会员访问)说明:

If T is a struct-type and I identifies an instance field of that struct-type:

• If E is a value, or if the field is readonly and the reference occurs outside an instance constructor of the struct in which the field is declared, then the result is a value, namely the value of the field I in the struct instance given by E.

• Otherwise, the result is a variable, namely the field I in the struct instance given by E.

T这里是目标类型,I是被访问的成员。

第一部分说,如果我们在构造函数之外访问结构的实例只读字段,结果是 value。在第二种情况下,实例字段不是只读的 - 结果是 variable.

然后“7.5.5函数成员调用”一节说(E这里是实例表达式,所以比如上面的onetwo):

• If M is an instance function member declared in a value-type:

If E is not classified as a variable, then a temporary local variable of E’s type is created and the value of E is assigned to that variable. E is then reclassified as a reference to that temporary local variable. The temporary variable is accessible as this within M, but not in any other way. Thus, only when E is a true variable is it possible for the caller to observe the changes that M makes to this.

正如我们在上面看到的,只读结构字段访问导致 value,而不是变量,因此,E 不属于变量。