Как мне обращаться с сеттерами на неизменяемых полях?


25

У меня есть класс с двумя readonly intполями. Они выставлены как свойства:

public class Thing
{
    private readonly int _foo, _bar;

    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public int Foo { get { return _foo; } set { } }

    public int Bar { get { return _bar; } set { } }
}

Тем не менее, это означает, что следующее является совершенно законным кодом:

Thing iThoughtThisWasMutable = new Thing(1, 42);

iThoughtThisWasMutable.Foo = 99;  // <-- Poor, mistaken developer.
                                  //     He surely has bugs now. :-(

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

Как следует Thingизменить класс, чтобы разработчики с меньшей вероятностью допустили вышеуказанную ошибку?

Бросить исключение? Использовать метод получения вместо свойства?



1
Спасибо, @gnat. Это сообщение (как вопрос и ответ на него ) , как представляется, говорить об интерфейсах, как в капитал I Interfaces. Я не уверен, что это то, что я делаю.
kdbanman

6
@gnat, это ужасный совет. Интерфейсы предлагают отличный способ обслуживания общедоступных VO / DTO, которые все еще легко тестировать.
Дэвид Арно

4
@gnat, я сделал, и вопрос не имеет смысла, поскольку OP, кажется, думает, что интерфейсы разрушат неизменность, что является бессмысленным.
Дэвид Арно

1
@gnat, этот вопрос касался объявления интерфейсов для DTO. Ответ, получивший наибольшее количество голосов, заключался в том, что интерфейсы не будут вредными, но, вероятно, ненужными.
Пол Дрейпер,

Ответы:


107

Зачем делать этот код легальным?
Вынь, set { }если ничего не получится .
Вот как вы определяете публичное свойство только для чтения:

public int Foo { get { return _foo; } }

3
Я не думал, что это было законно по какой-то причине, но, кажется, работает отлично! Отлично спасибо.
kdbanman

1
@kdbanman Вероятно, это как-то связано с тем, что вы видели в IDE в какой-то момент - возможно, с автозаполнением.
Panzercrisis

2
@kdbanman; что не разрешено (до C # 5) - это public int Foo { get; }(авто-свойство только с геттером), но public int Foo { get; private set; }есть.
Лоран Ла Ризза

@LaurentLARIZZA, ты пробежал мою память. Именно отсюда и произошло мое замешательство.
kdbanman

45

В C # 5 и ранее мы столкнулись с двумя вариантами неизменяемых полей, доступных через геттер:

  1. Создайте резервную копию только для чтения и верните ее через ручной метод получения. Эта опция безопасна (нужно явно удалить, readonlyчтобы уничтожить неизменность. Хотя она создала много кода-шаблона).
  2. Используйте авто-свойство с частным сеттером. Это создает более простой код, но легче случайно сломать неизменность.

Хотя с C # 6 (доступно в VS2015, который был выпущен вчера), мы теперь получаем лучшее из обоих миров: автоматические свойства только для чтения. Это позволяет нам упростить класс ОП:

public class Thing
{
    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; }
    public int Bar { get; }
}

Foo = fooИ Bar = barлинии действительны только в конструкторе (который достигает требования к надежным только для чтения) , а поле подложки подразумеваются, вместо того , чтобы быть явно определенно (который достигает более простой код).


2
И первичные конструкторы могут упростить это еще больше, избавившись от синтаксических накладных расходов конструктора.
Себастьян Редл


1
@DanLyons Черт, я с нетерпением ждал возможности использовать это во всей нашей кодовой базе.
Себастьян Редл

12

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

public class Thing
{
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; private set; }

    public int Bar { get; private set; }
}

1
« public int Foo { get; private set; }Этот класс больше не является неизменным, потому что он может измениться сам. Спасибо хоть!
kdbanman

4
Описанный класс является неизменным, поскольку он не меняется сам по себе.
Дэвид Арно

1
Мой настоящий класс немного сложнее, чем MWE, который я дал. Я после реальной неизменности , в комплекте с безопасностью потоков и внутренними гарантиями класса.
kdbanman

4
@kdbanman, а твоя точка зрения? Если ваш класс пишет только частным сеттерам во время построения, то он реализовал «реальную неизменность». readonlyКлючевое слово просто Защита от дурака, т.е. защищает от класса нарушающего immutablity в будущем; это не нужно для достижения неизменности, хотя.
Дэвид Арно

3
Интересный термин, мне пришлось его погуглить - пока-иго это японский термин, который означает «защита от ошибок». Вы правы! Это именно то, что я делаю.
kdbanman

6

Два решения.

Просто:

Не включайте сеттеры, как отметил Дэвид, для неизменяемых объектов только для чтения.

В качестве альтернативы:

Разрешить сеттерам возвращать новый неизменный объект, более подробный по сравнению с первым, но предоставляющий состояние с течением времени для каждого инициализированного объекта. Этот дизайн - очень полезный инструмент для безопасности и неизменяемости потоков, который распространяется на все обязательные языки ООП.

ПСЕВДОКОД

public class Thing
{

{readonly vars}

    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}

публичная статическая фабрика

public class Thing
{

{readonly vars}

    private Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public static with(int foo, int bar) {
        return new Thing(foo, bar)
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}

2
setXYZ - это ужасные названия для заводских методов, рассмотрим с помощью XYZ или тому подобное.
Бен Фойгт

Спасибо, обновил мой ответ, чтобы отразить ваш полезный комментарий. :-)
Мэтт Смитси

Вопрос был задан именно о методах установки свойств , а не о методах установки.
user253751

Правда, но ОП беспокоит изменчивое состояние от возможного будущего разработчика. Переменные, использующие частные только для чтения или частные конечные ключевые слова, должны самодокументироваться, что не является первоначальной ошибкой разработчиков, если это не понято. Понимание различных методов изменения состояния является ключевым. Я добавил другой подход, который очень полезен. В мире кода есть много вещей, которые вызывают чрезмерную паранойю, частное чтение только для чтения не должно быть одним из них.
Мэтт Смитси
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.