Почему я должен избегать использования свойств в С#?

В своей превосходной книге, CLR Via С#, Джеффри Рихтер сказал, что ему не нравятся свойства, и рекомендует не использовать их. Он дал некоторую причину, но я действительно не понимаю. Может ли кто-нибудь объяснить мне, почему я должен или не должен использовать свойства? В С# 3.0 с автоматическими свойствами это изменение?

В качестве ссылки я добавил отзывы Джеффри Рихтера:

• Свойство может быть доступно только для чтения или только для записи; доступ к полям всегда доступен для чтения и записи. Если вы определяете свойство, лучше всего предложить методы get и set accessor.

• Метод свойств может вызывать исключение; доступ к полю никогда не вызывает исключения.

• Свойство не может быть передано методу out или ref; поле может. Для Например, следующий код не будет компилироваться:

using System;
public sealed class SomeType
{
   private static String Name 
   {
     get { return null; }
     set {}
   }
   static void MethodWithOutParam(out String n) { n = null; }
   public static void Main()
   {
      // For the line of code below, the C# compiler emits the following:
      // error CS0206: A property or indexer may not
      // be passed as an out or ref parameter
      MethodWithOutParam(out Name);
   }
}

• Метод свойства может занять много времени; доступ к полям всегда завершается немедленно. Общей причиной использования свойств является выполнение синхронизации потоков, которая может остановить поток навсегда, и, следовательно, свойство не должно использоваться, если синхронизация потоков необходимо. В этой ситуации предпочтительным является метод. Кроме того, если ваш класс может быть доступ к удаленному (например, ваш класс происходит из System.MashalByRefObject), вызов метода свойства будет очень медленным, и, следовательно, метод предпочтителен для имущество. На мой взгляд, классы, полученные из MarshalByRefObject, никогда не должны использовать свойства.

• Если вы вызываете несколько раз подряд, метод свойств может возвращать другое значение каждый время; поле возвращает одно и то же значение каждый раз. Класс System.DateTime имеет только чтение Теперь свойство возвращает текущую дату и время. Каждый раз, когда вы запрашиваете это свойство, оно вернет другое значение. Это ошибка, и Microsoft хочет, чтобы они может исправить класс, сделав теперь метод вместо свойства.

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

• Метод свойств может потребовать дополнительной памяти или вернуть ссылку на что-то который фактически не является частью состояния объекта, поэтому изменение возвращаемого объекта не имеет воздействие на исходный объект; запрос поля всегда возвращает ссылку на объект, который гарантированно будет частью исходного состояния объекта. Работа с имуществом, которое возврат копии может быть очень запутанным для разработчиков, и эта характеристика часто не документировано.

Ответ 1

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

Лично я не согласен с ним в этом конкретном вопросе - я нахожу, что свойства делают код клиента намного проще для чтения, чем вызовы эквивалентных методов. Я согласен с тем, что разработчики должны знать, что свойства в основном маскируются, но я думаю, что обучение разработчиков об этом лучше, чем сделать код более трудным для чтения с помощью методов. (В частности, увидев Java-код с несколькими геттерами и сеттерами, вызываемыми в одном и том же выражении, я знаю, что эквивалентный код С# будет намного проще читать. Закон Деметры очень хорошо теоретически, но иногда foo.Name.Length действительно правильно использовать...)

(И нет, автоматически реализованные свойства на самом деле ничего не меняют.)

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

Ответ 2

Ну, давайте рассмотрим его аргументы один за другим:

Свойство может быть доступно только для чтения или только для записи; доступ к полям всегда читаемый и доступный для записи.

Это выигрыш для свойств, поскольку у вас есть более мелкий контроль доступа.

Метод свойств может исключение; доступ к полю никогда не срабатывает исключение.

Хотя это в основном верно, вы можете очень хорошо вызвать метод в не инициализированном поле объекта и создать исключение.

• Свойство не может быть передано как out или ref для метода; поле может.

Fair.

• Метод свойств может длиться время выполнения; доступ к полям всегда завершается немедленно.

Это также может занять очень мало времени.

• Если вы вызываете несколько раз подряд, метод свойства может возвращать другое значение каждый раз; поле возвращает одинаковое значение каждый раз.

Не верно. Как вы знаете, что значение поля не изменилось (возможно, другим потоком)?

В классе System.DateTime есть readonly Теперь свойство, которое возвращает текущая дата и время. Каждый раз, когда вы запросите это свойство, оно вернет различное значение. Это ошибка, и Microsoft хочет, чтобы они могли исправить класс, сделав теперь метод вместо свойства.

Если ошибка является второстепенной.

• Метод свойства может вызвать наблюдаемые побочные эффекты; доступ к полям никогда не делает. Другими словами, пользователь тип должен иметь возможность устанавливать различные свойства, определенные типом в любом порядок, который он или она выбирает без замечая какое-либо другое поведение в тип.

Fair.

• Метод свойств может потребовать дополнительной памяти или возврата ссылка на то, что не фактически часть состояния объекта, поэтому изменение возвращаемого объекта никакого эффекта на исходный объект; запрос поля всегда возвращает ссылка на объект, который гарантированно будет частью оригинала состояние объекта. Работа с свойство, которое возвращает копию, может быть очень запутанным для разработчиков, и это характеристика часто отсутствует документированы.

Большинство протестов можно было бы сказать и для Java-получателей и сеттеров - и мы долгое время были на них без таких проблем.

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

Ответ 3

Я не читал книгу, и вы не процитировали ее, которую не понимаете, поэтому я должен угадать.

Некоторым людям не нравятся свойства, потому что они могут заставить ваш код сделать что-то удивительное.

Если я наберу Foo.Bar, люди, читающие его, обычно ожидают, что это просто доступ к полю элемента класса Foo. Это дешевая, почти бесплатная операция, и она детерминирована. Я могу повторять это снова и снова, и каждый раз получаю тот же результат.

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

Это аналогичный аргумент в пользу того, почему Linus ненавидит С++. Ваш код может удивить читателя. Он ненавидит перегрузку оператора: a + b не обязательно означает простое добавление. Это может означать некоторые чрезвычайно сложные операции, как и свойства С#. Это может иметь побочные эффекты. Он может что-то сделать.

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

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

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

Однако у меня есть еще одна причина, чтобы найти их менее совершенными. Они не могут передаваться по ссылке на другие функции.

Поля могут передаваться как ref, позволяя вызываемой функции обращаться к ней напрямую. Функции могут передаваться как делегаты, позволяя вызываемой функции обращаться к ней напрямую.

Свойства... не могут.

Это отстой.

Но это не значит, что свойства являются злыми или не должны использоваться. Для многих целей они великолепны.

Ответ 4

Еще в 2009 году этот совет просто казался животным Who Moved My Cheese. Сегодня это почти смехотворно устарело.

Один очень важный момент, когда многие ответы, похоже, на цыпочках, но не совсем обращены к голове, - это то, что эти предполагаемые "опасности" свойств являются преднамеренной частью дизайна рамки!

Да, свойства могут:

  • Укажите различные модификаторы доступа для getter и setter. Это преимущество перед полями. Обычная модель состоит в том, чтобы иметь публичный геттер и защищенный или внутренний сеттер, очень полезный метод наследования, который невозможно достичь только по полям.

  • Выбросьте исключение. На сегодняшний день это остается одним из наиболее эффективных методов проверки, особенно при работе с инфраструктурами пользовательского интерфейса, которые включают концепции привязки данных. Гораздо труднее убедиться, что объект работает в правильном состоянии при работе с полями.

  • Возьмите много времени для выполнения. Действительное сравнение здесь - с методами, которые занимают одинаково длинные, а не поля. Нет никаких оснований для утверждения "предпочтительный метод", кроме личного предпочтения автора.

  • Возвращает различные значения из своего получателя при последующих исполнениях. Это почти похоже на шутку в такой непосредственной близости от точки, превозносившей достоинства параметров ref/out с полями, значение поля после вызова ref/out в значительной степени гарантировано от его предыдущего значения и непредсказуемо.

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

  • Потому что наблюдаемые побочные эффекты - это, конечно же, именно причина, по которой свойства были изобретены как языковая функция, в первую очередь. Microsoft Property Design указывает, что порядок настройки не должен иметь значения, поскольку в противном случае подразумевается временная связь. Конечно, вы не можете достичь временной связи только с полями, но это только потому, что вы не можете заставить какое-либо значимое поведение вообще произойти с отдельными полями, пока не будет выполнен какой-либо метод.

    Атрибуты доступа к ресурсам могут фактически помочь предотвратить определенные типы временной связи, заставив объект в действительное состояние до того, как будет выполнено какое-либо действие - например, если класс имеет StartDate и EndDate, а затем установить EndDate до того, как StartDate может снова заставить StartDate вернуться. Это справедливо даже в многопоточных или асинхронных средах, включая очевидный пример управляемого событиями пользовательского интерфейса.

Другие вещи, которые могут выполнять свойства, поля которых не могут включать:

  • Lazy loading - один из наиболее эффективных способов предотвращения ошибок при инициализации.
  • Изменить уведомления, которые в значительной степени являются основой для MVVM.
  • Inheritance, например, определяя абстрактный Type или Name, поэтому производные классы могут предоставлять интересные, но тем не менее постоянные метаданные о сами по себе.
  • Перехват, благодаря вышеперечисленному.
  • Индексаторы, которые каждый, кто когда-либо должен был работать с COM-взаимодействием и неизбежным изгибом вызовов Item(i), распознает как замечательный вещь.
  • Работайте с PropertyDescriptor, который необходим для создания дизайнеров и для фреймворков XAML в целом.

Рихтер явно является плодовитым автором и много знает о CLR и С#, но я должен сказать, похоже, когда он изначально написал этот совет (я не уверен, если он в его последних версиях - я искренне надеюсь, что нет), что он просто не хотел отпускать старые привычки и испытывал трудности с принятием конвенций С# (например, С++).

Я имею в виду, что его "свойства считаются вредным" аргументом, по существу, сводятся к одному утверждению: свойства выглядят как поля, но они могут не действовать как поля. И проблема с утверждением: это не правда, или, в лучшем случае, она вводит в заблуждение. Свойства не похожи на поля - по крайней мере, они не должны выглядеть как поля.

В С# есть два очень сильных соглашения о кодировании с аналогичными соглашениями, совместно используемыми другими языками CLR, и FXCop будет кричать на вас, если вы не будете следовать им:

  • Поля всегда должны быть приватными, а не публичными.
  • Поля должны быть объявлены в camelCase. Свойства - это PascalCase.

Таким образом, нет никакой двусмысленности в отношении того, является ли Foo.Bar = 42 аксессуаром свойства или средством доступа к полю. Это свойство accessor и должно рассматриваться как любой другой метод - он может быть медленным, он может генерировать исключение и т.д. Что характер Abstraction - это полностью зависит от усмотрения объявляющего класса, как реагировать. Дизайнеры классов должны применять принцип наименьшего удивления, но вызывающие лица не должны предполагать ничего об имуществе, за исключением того, что он делает то, что он говорит на олове. Это специально.

Альтернативой свойствам являются методы getter/setter везде. Это подход Java, и он был спорным с самого начала. Это прекрасно, если это ваша сумка, но это просто не то, как мы катимся в лагере .NET. Мы стараемся, по крайней мере, в рамках статически типизированной системы, чтобы избежать того, что Фаулер называет Синтаксический шум. Нам не нужны дополнительные круглые скобки, дополнительные get/set бородавки или дополнительные сигнатуры метода - нет, если мы сможем избежать их без потери ясности.

Скажите, что вам нравится, но foo.Bar.Baz = quux.Answers[42] всегда будет намного легче читать, чем foo.getBar().setBaz(quux.getAnswers().getItem(42)). И когда вы читаете тысячи строк этого дня, это имеет значение.

(И если ваш естественный ответ на вышеприведенный параграф должен сказать: "Конечно, это трудно прочитать, но было бы проще, если бы вы разделили его на несколько строк", то мне жаль говорить, что вы полностью упустили точку.)

Ответ 5

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

Автоматические свойства в С# 3+ упрощают синтаксис (a la syntatic sugar).

Ответ 6

Это всего лишь одно мнение. Я прочитал довольно много книг С#, и я еще не видел, чтобы кто-то еще говорил "не использовать свойства".

Я лично считаю, что свойства - одна из лучших вещей о С#. Они позволяют вам показывать состояние через любой механизм, который вам нравится. Вы можете лениво создать экземпляр в первый раз, когда что-то используется, и вы можете сделать валидацию при настройке значения и т.д. При использовании и написании их я просто думаю о свойствах как сеттерах и геттерах, которые имеют гораздо более хороший синтаксис.

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

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

например.

public class WorkerUsingMethod
{
   // Explicitly obvious that calculation is being done here
   public int CalculateResult()
   { 
      return ExpensiveLongRunningCalculation();
   }
}

public class WorkerUsingProperty
{
   // Not at all obvious.  Looks like it may just be returning a cached result.
   public int Result
   {
       get { return ExpensiveLongRunningCalculation(); }
   }
}

Я нахожу, что использование методов для этих случаев помогает сделать различие.

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

Скажите, что у вас есть такое свойство:

public int Result 
{ 
   get 
   { 
       m_numberQueries++; 
       return m_result; 
   } 
}

Теперь предположим, что у вас есть исключение, которое возникает, когда сделано слишком много запросов. Угадайте, что произойдет, когда вы начнете отлаживать и опрокидывать свойство в отладчике? Плохие вещи. Избегайте этого! Просмотр свойства изменяет состояние программы.

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

Ответ 7

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

Ответ 8

Я не могу не рассказать о деталях мнений Джеффри Рихтера:

Свойство может быть доступно только для чтения или только для записи; доступ к полям всегда доступен для чтения и записи.

Неверно: поля могут помечены только для чтения, поэтому для них может писать только конструктор объектов.

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

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

Ответ 9

Я не согласен с Джеффри Рихтером, но могу догадаться, почему ему не нравятся свойства (я не читал его книгу).

Несмотря на то, что свойства подобны методам (по реализации), как пользователь класса, я ожидаю, что его свойства ведут себя "более или менее", как публичное поле, например:

  • нет никакой длительной операции внутри свойства getter/setter.
  • свойство getter не имеет побочных эффектов (вызывает его несколько раз, не меняет результат)

К сожалению, я видел свойства, которые не вели себя так. Но проблема не в самих свойствах, а в людях, которые их реализовали. Так что это просто требует некоторого образования.

Ответ 10

Есть время, когда я считаю, что не использует свойства, и это в письменном виде .Net Compact Framework. Компилятор CF JIT не выполняет ту же оптимизацию, что и компилятор JIT на рабочем столе, и не оптимизирует простые устройства доступа к ресурсам, поэтому в этом случае добавление простого свойства приводит к размытию небольшого количества кода с использованием открытого поля. Обычно это не проблема, но почти всегда в мире Compact Framework вы против жестких ограничений памяти, поэтому даже крошечные сбережения, подобные этому, учитываются.

Ответ 11

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

Я когда-то видел свойство, называемое чем-то вроде Клиентов, которое внутренне открыло внепроцессный вызов в базу данных и прочитало список клиентов. Клиентский код имел "для (int я to Customers.Count)", который вызывал отдельный вызов базы данных на каждой итерации и для доступа выбранного Клиента. Это вопиющий пример, демонстрирующий принцип сохранения свойства очень легкий - редко больше, чем доступ к внутреннему полю.

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

Ответ 12

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

Ответ 13

Вызов методов вместо свойств значительно снижает читаемость вызывающего кода. В J #, например, использование ADO.NET было кошмаром, потому что Java не поддерживает свойства и индексаторы (которые по существу являются свойствами с аргументами). Полученный код был крайне уродливым, при этом метод пустой круглых скобок вызывался повсюду.

Поддержка свойств и индексаторов является одним из основных преимуществ С# над Java.