Работает ли использование открытых полей только для чтения для неизменяемых структур? - PullRequest
58 голосов
/ 19 мая 2011

Это правильный способ объявить неизменные структуры?

public struct Pair
{
    public readonly int x;
    public readonly int y;

    // Constructor and stuff
}

Я не могу понять, почему это может привести к проблемам, но я просто хотел попросить, чтобы убедиться.

В этом примере я использовал целые числа. Что если бы я использовал класс вместо этого, но этот класс также неизменен, как это? Это тоже должно работать нормально, верно?

public struct Pair
{
    public readonly (immutableClass) x;
    public readonly (immutableClass) y;

    // Constructor and stuff
}

(Помимо: я понимаю, что использование свойств более обобщенно и позволяет изменять, но эта структура предназначена буквально для хранения только двух значений. Меня просто интересует вопрос неизменности здесь.)

Ответы [ 4 ]

111 голосов
/ 19 мая 2011

Если вы собираетесь использовать структуры, рекомендуется сделать их неизменяемыми.

Создание всех полей только для чтения - это отличный способ помочь (1) документировать, что структура неизменна, и(2) предотвратить случайные мутации.

Однако, есть одна морщина, которая на самом деле по странному совпадению я планировал вести в блоге на следующей неделе.То есть: только для чтения в поле структуры - ложь .Можно ожидать, что поле только для чтения не может измениться, но, конечно, может.«только для чтения» в поле структуры - это чек для написания декларации без денег на его счете. Структура не владеет своим хранилищем, и это хранилище может изменяться.

Например, давайте возьмем вашу структуру:

public struct Pair
{
    public readonly int x;
    public readonly int y;
    public Pair(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
    public void M(ref Pair p)
    {
        int oldX = x;
        int oldY = y;
        // Something happens here
        Debug.Assert(x == oldX);
        Debug.Assert(y == oldY);
    }
}

Есть ли что-нибудьчто может произойти при «что-то здесь происходит», что приводит к нарушению отладочных утверждений?Конечно.

    public void M(ref Pair p)
    {
        int oldX = this.x;
        int oldY = this.y;
        p = new Pair(0, 0);
        Debug.Assert(this.x == oldX);
        Debug.Assert(this.y == oldY);
    }
...
    Pair myPair = new Pair(10, 20);
    myPair.M(ref myPair);

И что теперь происходит?Утверждение нарушено!«this» и «p» относятся к одному и тому же месту хранения.Место хранения видоизменено, и поэтому содержимое «этого» видоизменено, потому что это одно и то же.Структура не может обеспечить доступность только для чтения x и y, потому что структура не владеет хранилищем;хранилище - это локальная переменная, которая может изменять любое количество раз.

Вы не можете полагаться на инвариант, что поле readonly никогда не изменяется;единственное, на что вы можете положиться, это то, что вы не можете написать код, который напрямую его изменяет.Но с помощью такой хитрой работы вы можете косвенно изменить все, что вы хотите.

См. Также отличную статью Джо Даффи в блоге по этому вопросу:

http://joeduffyblog.com/2010/07/01/when-is-a-readonly-field-not-readonly/

5 голосов
/ 19 мая 2011

Это сделало бы его действительно неизменным.Полагаю, вам лучше добавить конструктор.
Если все его члены также неизменны, это сделает его полностью неизменным.Это могут быть классы или простые значения.

4 голосов
/ 30 июля 2018

Начиная с C # 7.2, теперь вы можете объявить всю структуру неизменной:

public readonly struct Pair
{
    public int x;
    public int y;

    // Constructor and stuff
}

Это будет иметь тот же эффект, что и маркировка всех полей, как readonly, и также сообщит самому компилятору, что структура неизменна. Это повысит производительность областей, где используется структура, за счет уменьшения количества защитных копий, которые делает компилятор.

Как отмечено в ответе Эрика Липперта , это не мешает полностью переназначить саму конструкцию и, таким образом, обеспечивает эффект изменения полей из-под вас. Для предотвращения этого можно использовать либо передачу по значению, либо использование нового модификатора параметра in:

public void DoSomething(in Pair p) {
    p.x = 0; // illegal
    p = new Pair(0, 0); // also illegal
}
1 голос
/ 19 мая 2011

Компилятор запретит присваивать readonly поля, а также свойства только для чтения.

Я рекомендую использовать свойства, доступные только для чтения, в основном из-за общедоступного интерфейса и привязки данных (которые не будут работать с полями). Если бы это был мой проект, я бы потребовал этого, если структура / класс общедоступна. Если он будет внутренним по отношению к сборке или закрытым по отношению к классу, я мог бы сначала его пропустить, а позже изменить его свойства на свойства только для чтения.

...