Як обробляти сетерів на незмінних полях?


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клас, щоб менші шанси розробити помилки вище?

Кинути виняток? Використовувати метод getter замість властивості?



1
Дякую, @gnat. Ця публікація (і питання, і відповідь), здається, говорить про інтерфейси, як у "Капіталі I" Interfaces. Я не впевнений, що це роблю.
kdbanman

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

4
@gnat, я це робив, і питання не має сенсу, оскільки ОП, здається, думає, що інтерфейси зруйнують незмінність, що є нісенітницею.
Девід Арно

1
@gnat, це питання стосувалося оголошення інтерфейсів для DTO. Відповідь вгорі - це те, що інтерфейси не будуть шкідливими, але, ймовірно, непотрібними.
Пол Дрейпер

Відповіді:


107

Чому зробити цей код законним?
Вийміть, set { }якщо це нічого не робить.
Ось як ви визначаєте публічну власність лише для читання:

public int Foo { get { return _foo; } }

3
Я не вважав, що це було законно чомусь, але, здається, працює чудово! Ідеально, дякую.
kdbanman

1
@kdbanman Це, ймовірно, має щось спільне з тим, що ви бачили IDE в один момент - можливо, автозаповнення.
Panzercrisis

2
@kdbanman; те, що не є законним (upto C # 5), є public int Foo { get; }(auto-property with only getter) Але public int Foo { get; private set; }є.
Лоран LA RIZZA

@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

Два рішення.

Простий:

Не включайте сетерів, як зауважив Девід, для незмінних об'єктів, що читаються тільки.

Як варіант:

Дозволити сеттерам повертати новий незмінний об'єкт, багатослівний порівняно з попереднім, але забезпечує стан у часі для кожного ініціалізованого об'єкта. Цей дизайн є дуже корисним інструментом для безпеки та незмінності нитки, який поширюється на всі необхідні мови OOP.

Псевдокод

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 тощо.
Бен Войгт

Дякую, оновив мою відповідь, щоб відобразити ваш корисний коментар. :-)
Метт Смітіс

Питання було задано конкретно про налаштування властивостей , а не про методи встановлення.
користувач253751

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