Почему Point.Offset () не выдает ошибку компилятора в структуре только для чтения? - PullRequest
0 голосов
/ 25 мая 2018

Возможно, я неправильно понимаю концепцию структуры только для чтения, но я думаю, что этот код не должен компилироваться:

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.) Я что-то упустил?

Ответы [ 2 ]

0 голосов
/ 25 мая 2018

У компилятора нет возможности выяснить, что метод Offset мутирует Point членов структуры.Однако 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 поле доступно только для чтения, а two - нет.Теперь, когда вы вызываете метод в readonly поле структуры - копия структуры передается этому методу как this.И если метод мутирует членов структуры - тогда члены этой копии мутируют.По этой причине вы не наблюдаете никаких изменений в one после вызова метода - он не был изменен, но копия была.

two поле не только для чтения, а сама структура (не копия)передан методу Offset, и поэтому, если метод мутирует члены - вы можете наблюдать их изменение после вызова метода.

Итак, это разрешено, потому что это не может нарушить контракт неизменности вашего readonly struct.Все поля readonly struct должны быть readonly, а методы, вызываемые в поле readonly struct, не могут изменить его, они могут только изменять копию.

Если вас интересует «официальный источник» для этой спецификации (7.6.4 Доступ к элементу) говорит, что:

Если T является структурным типом, и я идентифицирую поле экземпляра этого структурного типа:

• Если E является значением,или если поле доступно только для чтения и ссылка находится за пределами конструктора экземпляра структуры, в которой объявлено поле, то результатом является значение, а именно значение поля I в экземпляре структуры, заданное E.

• В противном случае результатом является переменная, а именно поле I в экземпляре структуры, заданном E.

T здесь - это тип цели, а I - доступ к члену.

Первая часть говорит, что если мы получим доступ к полю readonly экземпляра структуры, вне конструктора, результат будет value .Во втором случае, когда поле экземпляра не доступно только для чтения - результат равен переменная .

Тогда в разделе «7.5.5 Вызов члена функции» говорится (E здесь выражение экземпляра, например,one и two выше):

• Если M является элементом функции экземпляра, объявленным в типе значения:

Если E не классифицируется как переменная, тосоздается временная локальная переменная типа E, и этой переменной присваивается значение E.Затем E переклассифицируется как ссылка на эту временную локальную переменную.Временная переменная доступна как это в пределах M, но никаким другим способом.Таким образом, только когда E является истинной переменной, вызывающая сторона может наблюдать изменения, которые вносит в нее М.

Как мы видели выше, доступ к полям struct readonly struct приводит к value,не переменная, поэтому E не классифицируется как переменная.

0 голосов
/ 25 мая 2018

В поддержку ответа от Evk есть статья в отношении Point's method: Point.Offset ()

Обратите внимание, чтовызов метода Offset будет иметь эффект только в том случае, если вы сможете напрямую изменять свойства X и Y. Поскольку Point является типом значения, если вы ссылаетесь на объект Point с помощью свойства или индексатора, вы получаете копию объекта, а не ссылка на объект.Если вы попытаетесь изменить X или Y для ссылки на свойство или индексатор, произойдет ошибка компилятора.Аналогично, вызов Offset для свойства или индексатора не изменит базовый объект.Если вы хотите изменить значение точки, на которую ссылается свойство или индексатор, создайте новую точку, измените ее поля, а затем назначьте точку обратно свойству или индексатору.


По определению Point объявляется как struct: Точка

И последнее, но не менее важное: в блоге Microsoft есть статья , в которой говорится, чтоreadonly fields, которые на самом деле struct, их публичные свойства также доступны только для чтения.

Структура только для чтения - это структура, открытые члены которой только для чтения , а также параметр «this».

...