Является ли гарантия неизменности оправданием для выставления поля вместо свойства?

10

Общее руководство по C # - всегда использовать свойство над открытым полем. Это имеет смысл - выставляя поле, вы раскрываете много деталей реализации. Со свойством вы инкапсулируете эту деталь, чтобы она была скрыта от потребления кода, а изменения реализации отделены от изменений интерфейса.

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

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

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

Бен Ааронсон
источник
1
@gnat Просто чтобы уточнить, «Свойство ReadOnly» использует терминологию VB.NET, которая означает свойство без установщика, а не поле только для чтения. Для целей моего вопроса два варианта, которые сравнивает этот вопрос, эквивалентны - они оба используют свойства.
Бен Ааронсон

Ответы:

6

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

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

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

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

  1. Все, что пишет члену этого объекта, например: object.Location.X = 4возможно, только если Locationэто поле. Это не относится к полю только для чтения, потому что вы не можете изменить структуру только для чтения. (Предполагается, Locationчто это тип значения - в противном случае эта проблема не будет применяться в любом случае, потому readonlyчто не защищает от подобных вещей.)
  2. Любой вызов метода, который передает значение как outили ref. Опять же, это не очень важно для поля только для чтения, потому что outи refпараметры имеют тенденцию изменяться, и это ошибка компилятора в любом случае передавать значение только для чтения как out или ref.

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

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

Erik
источник
Из интереса, почему общедоступные статические поля доступны только для чтения? Это просто потому, что исключение методов экземпляра удаляет большинство сценариев использования свойств неизменяемых полей (таких как счетчик обращений, отложенная загрузка и т. Д.)?
Бен Ааронсон
Основной недостаток открытого поля readonly по сравнению со свойством readonly устранен - ​​статические члены не могут быть частью интерфейсов, поэтому они находятся на аналогичной основе. Статическому свойству только для чтения требуется либо хранилище, которое инициализируется, либо оно должно перестраивать свое возвращаемое значение при каждом вызове, тогда как поле только для чтения будет иметь более простой синтаксис и инициализироваться статическим конструктором. Поэтому я думаю, что общедоступное статическое поле только для чтения имеет потенциал для большей оптимизации с помощью JIT без каких-либо недостатков, которые вы обычно имели бы при представлении поля.
Эрик
2

По моему опыту, readonlyключевое слово - это не та магия, которой оно обещает.

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

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

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

Напомним, что ни readonlyсвойство non-public-setter не дает никакой гарантии «полностью инициализированного объекта». Другими словами, именно разработчик класса решает, что означает «полностью инициализированный» и как защитить его от непреднамеренного использования, и такая защита, как правило, не является надежной, что означает, что кто-то может найти способ обойти это.

rwong
источник
1
Я предпочитаю сохранять конструкторы простыми - см. Brendan.enrick.com/post/… , blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple, так что на самом деле readonly поощряет хороший стиль кодирования
AlexFoxGill
1
Конструктор может передавать readonlyполе в качестве параметра outили refв другие методы, которые затем могут свободно изменять его по своему усмотрению.
суперкат
1

Поля являются ВСЕГДА деталями реализации. Вы не хотите раскрывать детали своей реализации.

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

Свойства настолько просты в использовании и намного более гибки, чем открытые поля, что я не могу представить ни одного варианта использования, когда я выбрал бы c # в качестве языка и использовал бы открытые только для чтения поля в классе. Если бы производительность была настолько низкой, что я не смог бы принять удар дополнительного вызова метода, я бы, вероятно, использовал другой язык.

То, что мы можем что-то делать с языком, не означает, что мы должны что-то делать с языком.

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

Стивен
источник
1
Я понимаю важность создания будущей гибкости, но, конечно, если я напишу такой класс, как, скажем, Rationalс public, неизменяемыми Numeratorи Denominatorчленами, я могу быть чрезвычайно уверен, что этим участникам не потребуется ленивая загрузка или счетчик обращений или аналогичный?
Бен Ааронсон,
Но можете ли вы быть уверены, что реализация, использующая floatтипы для Numeratorи Denominator, не нуждается в изменении doubles?
Стивен
1
Ну, нет, они определенно не должны быть ни, floatни double. Они должны быть int, longили BigInteger. Может быть, те должны измениться ... но если это так, то они должны измениться и в публичном интерфейсе, поэтому использование свойств не добавляет инкапсуляции вокруг этой детали.
Бен Ааронсон,
-3

Нет, это не оправдание.

Использование readonly в качестве гарантии неизменности, а не оправдания, больше похоже на взлом.

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

Я думаю, что это будет плохая практика

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

Пример простого интерфейса: введите описание изображения здесь

Пабло Гранадос
источник
1
"использовать интерфейс" Как?
Бен Ааронсон,