Инкапсуляция означает, что состояние объекта происходит только через определенный интерфейс, и благодаря этому класс может убедиться, что это состояние всегда допустимо и соответствует назначению класса.
Таким образом, в некоторых случаях принципу инкапсуляции вполне соответствует публичное раскрытие поля - все возможные значения поля действительны со всеми другими возможными значениями всех других полей, и поэтому программист может принять активное решение разрешить поле чтобы ими мог свободно манипулировать внешний код.
Хотя эти случаи в основном ограничиваются классами, которые в основном представляют собой «старые простые данные». Они тоже не очень интересны в этом плане, так что хватит о них.
В других случаях на других языках можно было бы использовать методы получения и установки, что-то вроде int getId()получения значения и void setId(int val)его обновления.
Свойства позволяют нам использовать тот же синтаксис для чтения и записи с помощью таких методов, который мы использовали бы для чтения и записи поля. Это хороший синтаксический сахар, хотя и не жизненно важный.
(На самом деле, из-за того, как работает отражение, и таких случаев, DataBinder.Evalможет быть удобно иметь свойство, даже когда поле будет работать нормально, но это другое дело).
Вплоть до появления частных сеттеров (на самом деле, в C # 2 изменился синтаксис для наличия частного сеттера и общедоступного или защищенного получателя в одном блоке), у нас мог быть частный метод для выполнения работы частного сеттера, поэтому частные сеттеры на самом деле не нужны. Тем не менее, они удобны, поэтому, будучи всего лишь синтаксическим сахаром, они довольно полезны.
Инкапсуляция - это не вопрос того, являются ли ваши сеттеры (или геттеры) общедоступными, частными, защищенными или внутренними, а вопрос того, подходят ли они . Начните со значения по умолчанию, что каждое поле является частным (и в этом отношении readonly), а затем, при необходимости, добавьте элементы (будь то свойства или методы), которые изменяют эти поля, и убедитесь, что объект остается действительным при их изменении . Это гарантирует, что инвариант класса сохраняется, что означает, что правила, описывающие допустимый набор состояний, в которых он может находиться, никогда не нарушаются (конструкторы также помогают, гарантируя, что он запускается в таком допустимом состоянии).
Что касается вашего последнего вопроса, быть неизменным означает, что класс не имеет общедоступных, защищенных или внутренних установщиков и никаких общедоступных, защищенных или внутренних методов, которые изменяют какие-либо поля. Есть степени этого, в C # возможно три степени:
Все поля экземпляра класса есть readonly, поэтому даже частный код не может его изменить. Он гарантированно будет неизменным (все, что пытается его изменить, не компилируется), и, возможно, на его основе можно будет провести оптимизацию.
Класс неизменен снаружи, потому что ни один публичный член ничего не меняет, но при использовании не гарантируется, readonlyчто он не будет изменен изнутри.
Класс неизменен, если смотреть снаружи, хотя некоторые состояния меняются как деталь реализации. Например, поле можно запоминать, и, следовательно, хотя внешняя попытка получить его просто извлекает то же значение, первая такая попытка фактически вычисляет его, а затем сохраняет для извлечения при последующих попытках.
private File settingsFile = null;а затем в одном из конструкторов:if (settingsFile == null) { settingsFile = GetSettingsFile() };. Такой рефакторинг кода иногда заставлял меня плакать :). Тот факт, что вы можете установить член перед конструктором, не означает, что вы должны это делать, поскольку при наличии нескольких конструкторов это затрудняет соблюдение логики. Частные сеттеры заставляют вас устанавливать значения внутри конструктора или позже.