简体   繁体   中英

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

Perhaps I'm misunderstanding the concept of a readonly struct, but I would think this code should not compile:

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);
    }
 }

(I'm using C# 7.3.) Am I missing something?

There is no way for compiler to figure out that Offset method mutates Point struct members. However, readonly struct field is handled differently compared to non-readonly one. Consider this (not 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
    }
}

one field is readonly but two is not. Now, when you call a method on readonly struct field - a copy of struct is passed to that method as this . And if method mutates struct members - than this copy members are mutated. For this reason you don't observe any changes to one after method is called - it has not been changed but copy was.

two field is not readonly, and struct itself (not copy) is passed to Offset method, and so if method mutates members - you can observe them changed after method call.

So, this is allowed because it cannot break immutability contract of your readonly struct . All fields of readonly struct should be readonly , and methods called on readonly struct field cannot mutate it, they can only mutate a copy.

If you are interested in "official source" for this - specification (7.6.4 Member access) says that:

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 here is target type, and I is member being accessed.

First part says that if we access instance readonly field of a struct, outside of constructor, the result is value . In second case, where instance field is not readonly - result is variable .

Then section "7.5.5 Function member invocation" says ( E here is instance expression, so for example one and two above):

• 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.

As we saw above, readonly struct field access results in a value , not a variable and so, E is not classified as variable.

Supporting the answer from Evk , 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.


By the definition Point is declared as struct : Point

Last but not least, there is an article on Microsoft's blog, which describes that readonly fields , that are actually struct , their public properties are read-only also.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM