9

Что в какой ситуации выбирать?

В частности:

  • Если мне нужно только получать значение переменной, использовать геттер или свойство?
  • Если только присваивать — аналогично, сеттер или свойство?
  • Если вообще нет underlying (не знаю, как перевести) переменной, а значение вычисляется?

Пока меня не было, вопрос отредактировали. Почему Task.Result - свойство, а не метод? Что следовало бы поставить и почему? У него есть только геттер и оно не кешируется (наверно).


Связанный вопрос: Для чего нужны свойства?

  • 4
    На этот вопрос можно дать лаконичный ответ, более того - ему посвящен глава в Framework Design Guidelines. У 4 из 5-ти голосовавших нет даже плашки C# в профиле, имейте ж совесть! –  Jul 30 '15 at 07:35
  • @PashaPash: я думаю закрывают в основном за качество оформления вопроса - всего 1 строка. Надо добавить БОЛЬШЕ деталей к вопросу, обозначить суть проблемы, а не просто так спросить "что лучше, конь или плутон, что лучше выбрать" – Kromster Jul 30 '15 at 07:56
  • 1
    @KromStern: для знакомых с С# это не «конь и плутон», а два коня разных пород. – Nick Volynkin Jul 30 '15 at 08:31
  • @PashaPash, причина закрытия изначально может и не удачно была выбрана, но вердикт "требуется правка" подходит и сюда. Вопрос в текущей формулировке не соответствует формату SO, поэтому ему нужна правка. И здесь не важно есть c# в профиле или нет. – BOPOH Jul 30 '15 at 09:05
  • 2
    @BOPOH вопрос для знающих C# достаточно понятен и однозначен. Причем он возникает при переходе java на C# - потому что в java вместо свойств используются методы. Это стандартный вопрос новичка. Из категории "как отправить почту в php". И на него можно дать "однозначный и лаконичный" ответ. Я не понимаю, какую правку вы хотели бы видеть. –  Jul 30 '15 at 09:14
  • Плюсую @PashaPash, есть ведь где-то конкретные критерии куда совать свойство, а куда - метод. И насчет java ты, кстати, угадал. – Smit Johnth Aug 02 '15 at 03:29

5 Answers5

12

С точки зрения рантайма разницы, конечно, нет. Свойство есть не более чем пара из двух функций — геттера и сеттера, причём эти функции, формально говоря, вовсе не обязаны читать/устанавливать какое-либо поле объекта.

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

У объекта есть данные, которыми он обладает, и поведение. То, что является данными, моделируется свойствами. То, что является поведением, моделируется методами. При этом совершенно не важно, представлены ли данные объекта полем, несколькими полями, или вообще вычисляются на лету — это подробности имплементации, которые никому «снаружи» не интересны. Это является одной из причин того, почему «наружу» нужно выставлять свойства, а не поля.

Допустим, у вас есть собака. У неё есть цвет шерсти, кличка, и она умеет выполнять команды. Теперь давайте рассмотрим разницу.

Кличка — это есть свойство собаки: это нечто, что есть объект (строка), присущий самой собаке. Её можно установить (например, новый хозяин собаки может поменять кличку), и прочитать. Соответственно, её нужно моделировать свойством:

public string Name { get; set; }

Цвет шерсти собаки — тоже её свойство. Но его невозможно «установить» снаружи (отвлечёмся от возможности перекрасить собаку: цвет краски и цвет шерсти — не одно и то же). Поэтому его нужно моделировать свойством, которое доступно только на чтение:

private readonly Color color;
public Color Color { get { return color; } }

(или для вышедшего пару дней назад C# 6 можно просто

public Color Color { get; }

поскольку значение можно установить в конструкторе).

Теперь, выполнение команды. Это не свойство самой по себе собаки, это ей поведение. Таким образом, правильно будет моделировать выполнение команды так:

public void ExecuteCommand(DogCommand command) { ... }

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

private DogCommand currentCommand = null;
public DogCommand CurrentCommand
{
    get { return currentCommand; }
    set
    {
        if (currentCommand != null)
            StopExecuting();
        currentCommand = value;
        if (currentCommand != null)
            StartExecuting();
    }
}

потому, что команда — это не неотъемлемое свойство самой собаки, а лишь её поведение. Правильно будет, например, так:

bool isExecutingCommand = false;
DogCommand currentCommand = null;

public void ExecuteCommand(DogCommand command)
{
    if (isExecutingCommand)
        CancelCommand();
    isExecutingCommand = true;
    currentCommand = command;
    StartCommand(command);
}

public DogCommand GetCurrentExecutingCommand()
{
    return isExecutingCommand ? currentCommand : null;
}
VladD
  • 206,799
11

Методы - это действия. Свойства - это данные.

Вопрос подробно разобран в MSDN: Property Usage Guidelines: Properties vs. Methods

Если то, что вы пишете выглядит как кусок данных объекта - делайте его свойством. Например Control.Name.

Если то, что вы пишете:

  • конвертирует данные (.ToString())
  • занимает долго времени
  • дает сайд-эффекты при получении
  • выдает разные результаты при двух вызовах подряд
  • зависит от порядка вызова других свойств или методов
  • статично, но может возвращать разные результаты
  • возвращает массив

то стоит использовать метод.

  • 1
    Свойства - это тоже методы. Свойства компилируются в методы - гетеры и сетеры, причём теоретически даже гетер и сетер могут быть с более чем одним параметром, потому что они - самые что ни есть обычные методы со всеми их прелестями, и это работает, если писать на IL, но на C# такую конструкцию не написать. – Alexander Vasilyev Jul 29 '15 at 23:10
  • 2
    Я бы ответил так: свойства - это более удобный способ написания методов GetSomeValue() и SetSomeValue(value). Соответственно, отвечая на вопрос автора: для удобства и лаконичности стоит использовать свойства вместо методов, если их семантика GetValue и SetValue(value). – Alexander Vasilyev Jul 29 '15 at 23:12
  • @AlexanderVasilyev в ответе (переведенном с Framework Design Guidelines) как раз и перечисены случаи, когда даже в случае семантики GetValue стоит использовать метод. Жаль что вопрос закрыли. –  Jul 30 '15 at 07:30
  • Попробовал уточнить вопрос, посмотрите. – Nick Volynkin Jul 30 '15 at 09:19
  • @NickVolynkin спасибо :) –  Jul 30 '15 at 09:22
  • 1
    @AlexanderVasilyev запостите свой коммент как ответ? –  Jul 30 '15 at 09:23
  • @AlexanderVasilyev: тоже рекомендую запостить комментарий как ответ. – Nick Volynkin Jul 30 '15 at 09:25
  • 1
    Вообще надо сделать один вики-ответ и дополнять всем – Kromster Jul 30 '15 at 09:34
  • @NickVolynkin Спасибо, так и сделал. Когда постил комменты были запрещены постинги ответов почему-то к этому вопросу. – Alexander Vasilyev Jul 31 '15 at 12:46
  • 1
    @AlexanderVasilyev: это потому что вопрос был закрыт. Теперь его переоткрыли. http://chat.stackexchange.com/transcript/message/23103058#23103058 , http://ru.stackoverflow.com/posts/438747/revisions – Nick Volynkin Jul 31 '15 at 12:47
  • Не соглашусь с безусловностью некоторых пунктов. Так, уведомление об изменении значения - это тоже сайд-эффект, но он совершенно не мешает свойству быть настоящим свойством. Да и разные результаты при двух вызовах подряд - это тоже нормально для такого свойства, как DateTime.Now :) – Pavel Mayorov Jul 31 '15 at 13:09
  • @PavelMayorov не просто сайд эффекты, а именно сайд эффекты при получениии. DateTime.Now скорее исключение, но я не знаю как его аккуратно вписать в список. –  Jul 31 '15 at 17:03
  • Автодетект зависимостей - вполне себе сайд-эффект, который, тем не менее, может присутствовать в геттерах – Pavel Mayorov Jul 31 '15 at 18:27
  • А почему Task.Result свойство, а не метод? – Smit Johnth Feb 08 '17 at 13:43
  • @SmitJohnth потому что оно соответствует почти всем признакам свойства, кроме, возможно "занимает долго времени". Но "долго" - понятие относительное. –  Feb 08 '17 at 13:46
  • Он вообще кеширует результат? Он может долго выполняться, может вообще блокироваться на i/o. – Smit Johnth Feb 08 '17 at 13:47
  • @SmitJohnth да, кэширует. и оно не выполняется напрямую - т.е. само по себе свойсво Results не создает какую-то долгую нагрузку. Оно просто не возвращает управление, пока таск не доработает до конца. Но само по себе свойство всегда работает быстро ) –  Feb 08 '17 at 13:50
  • Я чет не понял :) Где выполняется таск - в том же треде или в другом? – Smit Johnth Feb 09 '17 at 10:38
  • @SmitJohnth таск вообще может не выполняться (созданный через Task.FromResult), или может выполняться в смысле "не выполнять код на процессоре" - это может быть таск, который завершится после завершения IO (но это не значит, что при этом таск "блокируется" на IO, или что ему вообще выделен какой-то поток) –  Feb 09 '17 at 10:55
  • Но может ведь и выполняться и занимать процессорное время. В любом случае, он может блокировать. Так почему тогда свойство? – Smit Johnth Feb 09 '17 at 10:56
  • @SmitJohnth потому что выполняется и занимает процессорное время сам таск, а не геттер Result. Геттер Result просто блокируется до готовности результата. Альтернативное объяснение - разработчики фреймворка не читали мой ответ :) (да, я знаю что это неточность перевода с моей стороны, но мне больше нравится такой вариант) –  Feb 09 '17 at 11:50
3

Свойства - это тоже методы. Свойства компилируются в методы - гетеры и сетеры, причём даже гетер и сетер могут быть с более чем одним параметром, потому что они - самые что ни есть обычные методы со всеми их прелестями, но на C# такую конструкцию не написать, а вот на IL или VB пожалуйста: https://msdn.microsoft.com/en-us/library/bc3dtbky.aspx

Итого, свойства - это более удобный способ написания методов GetSomeValue() и SetSomeValue(value). Соответственно, отвечая на вопрос автора: для удобства и лаконичности стоит использовать свойства вместо методов, если их семантика GetValue и SetValue(value).

1

На этот вопрос не существует безусловно правильного ответа, поэтому всё, что будет написано в качестве ответов — не более, чем мнения.

Итак, личное мнение: свойства позволяют упростить работу с объектом. По большому счёту, всегда, когда есть возможность, лучше использовать свойства вместо методов, за исключением нескольких случаев.

Например, возможность есть, если у вас метод без параметров возвращает какое-то значение, не меняя состояния объекта — это свойство только для чтения. Здесь неважно, возвращается ли обычное скрытое поле или результат вычислений.

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

Другое исключение касается времени выполнения вычислений. Если время может оказаться "достаточно" большим, правильнее написать метод, а не свойство. Насколько понимаю, дело здесь в "психологии": когда вы видите Tree.Depth, то конструкция воспринимается как обычное получение значения поля, хотя в действительности может быть рекурсивным методом с большим временем выполнения.

1

Поскольку сами свойства это не более чем "сахар", то это часто вопрос личных предпочтений, но в целом всё уже правильно расписал PashaPash. Свойства должны выглядеть так, будто это поля вашего объекта, свойства лишь позволяют удобно их скрывать. Конкретно по вашим вопросам:

  • Как правило свойство.
  • Хотя это возможно, но не рекомендуется создавать свойства только для записи, потому в данном случае рекомендуется метод.
  • Зависит от способа вычисления, в общем случае если задействуются только другие свойства (т.е. статические данные объекта) то свойство, в противном случае - метод.
Petr Abdulin
  • 2,130