Для чего нужны свойства?

1,00
р.
Допустим есть это:
private int a { get set }
Какой в этом смысл, если я могу сделать так:
public int a

Ответ
Смотрите, какие есть преимущества у свойства перед полем.
Если ваше свойство определено так:
public int A { get set }
— то непосредственных выгод, конечно, нету. Но выгоды придут позже.
Вы можете навесить свою логику на запись и считывание значения. Применений может быть море. Например, вы хотите посчитать, сколько раз считывалось значение: private int a private int readcount_a = 0 public int A { get { readcount_a++ return a } set { a = value } }
Вы можете сделать триггер на изменение поля: class Data : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged
private int a public int A { get { return a } set { if (a == value) return a = value RaisePropertyChanged() } }
private void RaisePropertyChanged([CallerMemberName] string propertyName = null) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)) } }
Вы можете залогировать все изменения поля: public int A { get { return a } set { Trace.TraceInformation("Changing value of a from {0} to {1}", a, value) a = value } }
Вы можете навесить проверку значения на корректность при записи, или ленивую инициализацию при чтении. class Data { private string a = null public string A { get { return a ?? (a = LazyComputeInitialA()) } set { if (value == null) throw new ArgumentException(nameof(A) + " cannot be null", nameof(A)) a = value } } }
В конце-концов, вы можете не расходовать память на значение, если в большинстве случаев оно одинаковое (как это сделано у DependencyProperty): class Data { static Dictionary aValues = new Dictionary()
public int A { get { int result if (aValues.TryGetValue(this, out result)) return result else return -1 // default value } set { aValues[this] = value } } }
[Если вы захотите воспользоваться этим кодом в своём проекте, применяйте улучшенный вариант: static readonly ConditionalWeakTable> aValues = new ConditionalWeakTable>()
public int A { get => aValues.TryGetValue(this, out var tmp) ? tmp.Value : -1 // default value set => aValues.GetOrCreateValue(this).Value = value }
В этом варианте исправлены недостатки варианта с Dictionary: в словаре более не хранится ссылка на объект Data (она препятствует удалению объекта сборщиком мусора), а также одновременный доступ из разных потоков не приведёт к проблемам с Dictionary, т. к. ConditionalWeakTable потокобезопасен.]
Вы хотите сделать разную степень видимости у геттера и сеттера, так что например только ваш класс и его наследники смогут установить значение, а считать смогут все. (Это, пожалуй, самое лучшее применение свойств, на мой вкус.) public int A { get protected set }
Вы можете вообще не определять setter, и возвращать какую-то высчитанную вами величину: public int A { get { return b + c } }
(впрочем, такое можно сделать и при наличии сеттера). Например, вы можете предоставлять данные в разных форматах: public double Radians { get set } public double Degrees { get { return Radians * 180.0 / Math.PI } set { Radians = value * Math.PI / 180.0 } }
Вы можете установить брейкпойнт на запись или чтение свойства! Брейкпойнты на запись или чтение данных в отладчике управляемого кода Visual Studio пока (по крайней мере до текущей на данный момент версии Visual Studio 2017, версия 15.7) не поддерживаются. Обновление: начиная с .NET Core 3.0, брейкпойнты на изменение данных поддерживаются!
Вы можете задать наличие свойства в интерфейсе, в отличие от поля: interface ISupportsA { int A { get } }
class Data : ISupportsA { public int A { get set } }
Если вы применяете реализацию методов в интерфейсах, появившуюся в C# 8, вы сможете реализовать свойство, используя технику с ConditionalWeakTable, описанную выше. Также при помощи этой техники вы сможете добавить в класс данные в методах расширения.
Вы можете объявить свойство виртуальным! То есть вы сможете переопределить поведение свойства в классах-наследниках. Попробуйте-ка сделать такое с полем.
Свойства не хуже полей в том смысле, что вы можете заставить свойство работать так, будет это просто поле (public int A { get set }), но вы не сможете поле заставить работать как свойство. То есть практически всегда лучше «наружу» выставлять свойство, а не поле.
XML-сериализация и WPF-овский Binding работает лишь со свойствами, но не с полями. Да, это можно считать ошибкой во фреймворке, но фактически это так.


Но не излишни ли свойства в языке? Кажется, что вместо свойства можно определить просто две функции:
class Data { private int a public int GetA() { return a } public int SetA(int a) { this.a = a } }
Ответ на это таков.
Во-первых, одно свойство вместо двух функций представляет собой логическую группу. В хорошем языке вы говорите то, что думаете. На самом деле вы предоставляете пользователю «переменную» A с дополнительной, часто невидимой снаружи семантикой. Значит, и выглядеть она должна как одна переменная, чтобы пользователи класса думали в тех же терминах, что и вы.
Во-вторых, это читаемость текста. Сравните код со свойствами:
player.car.speed++
и без них:
getPlayer().getCar().setSpeed(getPlayer().getCar().getSpeed()+1)
Что легче воспринимается?

Справедливости ради, нужно отметить и недостатки свойств по сравнению с полями.
Свойства нельзя использовать как out/ref-параметр, поля можно.
Доступ к полям очень быстр, а вот доступ к свойствам может быть медленным, если код внутри геттера/сеттера медленный. Однако, медленный сеттер или (ещё хуже) геттер считаются порочной практикой, их рекомендуется избегать, чтобы не разрушать ментальную модель «переменная с небольшим довеском».
Доступ к свойству может выбросить исключение или зависнуть, в то время как более простые поля ведут себя крайне просто. Конечно, правильно написанное свойство не будет зависать, а исключения я бы порекомендовал выбрасывать только в случаях, когда пользователь класса нарушил контракт на доступ к полю.


Ещё одно тонкое отличие свойства от поля состоит в том, что геттер возвращает вам копию значения, в то время как при работе с полем вы получаете доступ непосредственно к переменной. При работе со полями reference-типов (то есть, тип которых есть класс) практической разницы нет, так как работа с объектом по копии ссылки не отличается от работы по оригиналу ссылки. Разница, однако, есть, когда поле является изменяемой структурой (хотя, сами по себе изменяемые структуры — плохая идея). Пример случая, когда это важно, во фрагменте кода ниже.

Часто считают, что можно для начала объявить данные как поле, а потом, при необходимости, «превратить» его в свойство. Это лишь отчасти верно: при этом вы теряете бинарную совместимость. Код, который использовал ваш класс, должен быть перекомпилирован, так как на уровне скомпилированного кода обращение к полю и к свойству — не одно и то же. Кроме того, смысл кода может поменяться, приводя к тонким ошибками. Пример из статьи по ссылке выше:
using System
struct MutableStruct { public int Value { get set } public void SetValue(int newValue) { Value = newValue } }
class MutableStructHolder { public MutableStruct Field public MutableStruct Property { get set } }
class Test { static void Main(string[] args) { MutableStructHolder holder = new MutableStructHolder() // Меняет значение holder.Field holder.Field.SetValue(10) // Получает *копию* holder.Property и изменяет её holder.Property.SetValue(10) Console.WriteLine(holder.Field.Value) // 10 Console.WriteLine(holder.Property.Value) // 0 } }

Кстати, согласно Википедии, геттер и сеттер правильно называть акцессор и мутатор соответственно. Вы об этом знали? [Хотя, MSDN пишет просто «методы доступа».]