Почему я должен избегать использования свойств в C #? - PullRequest
100 голосов
/ 29 марта 2009

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

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

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

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

• Свойство не может быть передано как параметр 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 желает, чтобы они можно исправить класс, сделав метод Now вместо свойства.

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

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

Ответы [ 14 ]

172 голосов
/ 29 марта 2009

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

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

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

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

35 голосов
/ 29 марта 2009

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

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

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

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

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

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

Fair.

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

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

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

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

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

Если это ошибка, то незначительная.

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

Fair.

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

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

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

18 голосов
/ 29 марта 2009

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

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

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

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

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

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

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

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

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

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

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

Это отстой.

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

17 голосов
/ 21 сентября 2013

Еще в 2009 году этот совет выглядел просто как восторг от сорта , который сдвинул мой сыр . Сегодня это почти смехотворно устарело.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. Поля должны всегда быть частными, никогда общедоступными.
  2. Поля должны быть объявлены в camelCase. Свойства являются PascalCase.

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

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

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

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

11 голосов
/ 29 марта 2009

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

Автоматические свойства в C # 3+ только немного упрощают синтаксис (а-ля синтетический сахар).

9 голосов
/ 29 марта 2009

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

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

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

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

1009 * Е.Г. *

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; 
   } 
}

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

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

6 голосов
/ 29 марта 2009

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

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

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

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

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

6 голосов
/ 29 марта 2009

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

5 голосов
/ 29 марта 2009

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

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

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

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

4 голосов
/ 10 августа 2009

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

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

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

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