Как лучше всего использовать свойства .NET?

Я немного смущен тем, сколько я ДОЛЖЕН делать со свойствами. Я слышал, что свойства всегда должны представлять логическое свойство класса. Get и Set почти никогда не должны генерировать исключения, за исключением ArgumentOutOfRange. Это правда? Является ли следующий пример совершенно неправильным?

public bool DeviceRegistered
{
    get{ return _Registered;}
    set
    {
        if(value)
        {
            RegisterDevice();
            _Registered = true;
        }
        else
        {
            UnRegisterDevice();
            _Registered = false;
        }
    }
}

Кроме того, если метод в том же классе хочет изменить значение свойства, должен ли он пройти через средство доступа set свойства или просто изменить частную переменную _Registered напрямую?

Если у вас есть какие-либо дополнительные советы по использованию свойств, пожалуйста, укажите! Спасибо


person PICyourBrain    schedule 25.02.2010    source источник


Ответы (7)


Вот ссылка на Руководство по разработке свойств из MSDN. Обратите особое внимание на свойство и метод раздел.

Исходя из моего личного опыта, вы не должны использовать свойства для выполнения большого объема работы. Они должны возвращать информацию, которая уже была получена. В настоящее время я работаю над базой кода, которая имеет множество лениво загруженных свойств, которые извлекают информацию из веб-службы. Просмотр свойств класса во время отладки приводит к оценке всех свойств, что приводит к истечению времени функциональной оценки и сбою процесса ASP.NET.

person Richard Nienaber    schedule 25.02.2010
comment
Что бы вы порекомендовали сделать вместо этого для свойств с ленивой загрузкой? - person Greg; 25.02.2010
comment
Мы остановились на использовании сервисов для заполнения свойств. В большинстве случаев мы обнаружили, что объект использовался только один раз для каждого запроса. Если он используется более одного раза, служба сохраняет его в кеше (например, Http.Items). Эта логика извлечения из кэша также может быть абстрагирована и использоваться совместно службами по мере необходимости. - person Richard Nienaber; 25.02.2010
comment
@Greg: сделайте частное резервное поле System.Lazy<T>, return _lazyValue.Value; из свойства и отметьте свойство как [DebuggerBrowsable(DebuggerBrowsableState.Never)]. - person Sam Harwell; 25.02.2010
comment
@ 280Z28: только .NET 4.0: msdn.microsoft.com /en-us/library/dd642331(VS.100).aspx - person John Saunders; 25.02.2010
comment
@John Saunders: Вы можете получить его под MS-PL в исходном дистрибутиве MEF (ссылка 1) или использовать лучший вариант в System.Threading.dll из Reactive Extensions для .NET (ссылка 2). 1) mef.codeplex.com 2) msdn.microsoft.com/en-us/devlabs/ee794896.aspx - person Sam Harwell; 25.02.2010
comment
@ 280Z28: Спасибо. Эта квалификация была необходима, поскольку ее нет в .NET 3.5. - person John Saunders; 25.02.2010

В этом случае я думаю, что более логично использовать методы, потому что вы выполняете действие.

private volatile bool _DeviceRegistered;
private readonly object _Lock = new object();

public bool DeviceRegistered
{
    get { return _DeviceRegistered; }
}

public void RegisterDevice()
{
    lock (_Lock) // A good idea to lock
    {
        // ........
        _DeviceRegistered = true;
    }
}

public void UnregisterDevice()
{
    lock (_Lock)
    {
        // ........
        _DeviceRegistered = false;
    }
}
person ChaosPandion    schedule 25.02.2010
comment
Вы, вероятно, также захотите заблокировать свой метод доступа к свойству get, чтобы ........ не выполнялся (возможно, в отдельном потоке) при чтении свойства. - person Jesse C. Slicer; 25.02.2010
comment
@Jesse - вопрос в том, хотим ли мы связать другие потоки при регистрации устройства? Я думаю, чтобы быть в безопасности, вы правы, но это действительно зависит от кода, который мы не видели. - person ChaosPandion; 25.02.2010

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

Альтернативой может быть метод "get", и это нормально... но мне нравится использовать свойство, когда я не хочу, чтобы вызывающая сторона была обременена идеей, что есть работа, связанная с получением/определением значения.

person lance    schedule 25.02.2010

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

person Shawn Steward    schedule 25.02.2010

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

public void Register()
{
  ...do some stuff...
  this.Registered = true;
}

public void Unregister()
{
  ...do some stuff...
  this.Registered = false;
}

public bool Registered { get; private set; }

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

person tvanfosson    schedule 25.02.2010

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

Бывают случаи, когда доступ к полю (приватный) подходит, но я всегда отдаю предпочтение свойству, если только нет веских причин для доступа к полю.

person MaLio    schedule 25.02.2010

Вот несколько правил, которые я понял со временем. Большинство из них являются моими мнениями, но мне нравится думать, что это хорошие идеи. :) Изменить: я только что понял, что большинство из них описаны в Использование ресурсов.

Добытчики

  • Вы должны предпочесть, чтобы геттеры не изменяли состояние. Если у вас есть геттер, который изменяет состояние программы, пометьте его [DebuggerBrowsable(DebuggerBrowsableState.Never)].
  • Предпочитайте, чтобы геттеры могли вызываться из любого потока.
  • Предпочитайте, чтобы геттеры вычислялись тривиально, так как они используются таким образом, что люди верят, что они не понесут потери производительности. Если их выполнение может занять некоторое время, пометьте их либо атрибутом DebuggerBrowsable, как указано выше, либо с помощью [DebuggerDisplay("Some display text")] (где текст тривиально вычисляется). Пренебрежение последним может отрицательно сказаться на производительности отладчика.
  • Getters can throw at least the following exceptions:
    • InvalidOperationException
    • ObjectDisposedException

Сеттеры

  • Предпочтите, чтобы всякий раз, когда вы устанавливаете два свойства подряд, не имело значения, какое из них будет первым.
  • Если у установщика есть предварительное условие, которое нельзя проверить с помощью общедоступных свойств, оно должно быть помечено как защищенное или закрытое.
  • Можно ограничить вызов сеттеров определенным потоком, но если вы это сделаете, это должно быть задокументировано, и ваш объект должен либо реализовывать ISynchronizeInvoke или предоставить объект Dispatcher.
  • Setters can throw at least the following exceptions:
    • An ArgumentException (ArgumentNullException, ArgumentOutOfRangeException, etc. as appropriate)
    • InvalidOperationException
    • ObjectDisposedException
person Sam Harwell    schedule 25.02.2010