为什么 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
声明为 struct
:Point
最后但同样重要的是,微软的博客上有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
这里是实例表达式,所以比如上面的one
和two
):
• 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
不属于变量。
也许我误解了只读结构的概念,但我认为这段代码不应该编译:
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。) 我错过了什么吗?
支持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
声明为 struct
:Point
最后但同样重要的是,微软的博客上有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
这里是实例表达式,所以比如上面的one
和two
):
• 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
不属于变量。