Работает ли использование открытых полей только для чтения для неизменяемых структур?


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

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

    // Constructor and stuff
}

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

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

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

    // Constructor and stuff
}

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

4 53

4 ответа:

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

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

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

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

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

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

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

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

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

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

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

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

начиная с 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
}