为什么Point.Offset()没有在readonly结构中给出编译器错误?

Eri*_*ers 4 .net c# struct readonly-attribute

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

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);
    }
 }
Run Code Online (Sandbox Code Playgroud)

(我正在使用C#7.3.)我错过了什么?

Evk*_*Evk 6

编译器无法确定该Offset方法是否会改变Pointstruct成员.但是,readonly与非只读的字段相比,struct字段的处理方式不同.考虑这个(不是只读)结构:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

one字段是只读但two不是.现在,当您在readonlystruct字段上调用方法时- struct 的副本传递给该方法this.如果方法改变了struct成员 - 那么这个副本成员就会发生变异.出于这个原因,你没有观察到one调用after方法的任何更改- 它没有被更改但是复制了.

twofield不是readonly,struct本身(不是copy)传递给Offset方法,因此如果方法改变成员 - 你可以观察它们在方法调用后改变.

所以,这是允许的,因为它不能破坏你的不变性合同readonly struct.readonly struct应该是所有字段readonly,并且readonly struct字段上调用的方法不能改变它,它们只能改变副本.

如果您对"官方来源"感兴趣 - 规范(7.6.4会员访问)说:

如果T是struct-type并且我标识了该struct-type的实例字段:

•如果E是一个值,或者该字段是readonly并且引用发生在声明该字段的struct的实例构造函数之外,那么结果是一个值,即给定struct结构中的字段I的值由E.

•否则,结果是一个变量,即E给出的struct实例中的字段I.

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

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

然后部分"7.5.5函数成员调用"表示(E这里是实例表达式,因此,例如onetwo以上):

•如果M是在value-type中声明的实例函数成员:

如果E未被归类为变量,则创建E类型的临时局部变量,并将E的值分配给该变量.然后将E重新分类为对该临时局部变量的引用.临时变量在M中可以访问,但不能以任何其他方式访问.因此,只有当E是真变量时,呼叫者才有可能观察到M对此做出的变化.

正如我们在上面看到的那样,readonly struct field access导致a value而不是变量,因此E不被归类为变量.