Як реалізувати властивість лише для читання


80

Мені потрібно реалізувати властивість лише для читання для мого типу. Більше того, значення цієї властивості буде встановлено в конструкторі, і воно не буде змінено (я пишу клас, який виставляє власні маршрутизовані команди інтерфейсу для WPF, але це не має значення).

Я бачу два способи зробити це:

  1. class MyClass
    {
        public readonly object MyProperty = new object();
    }
    
  2. class MyClass
    {
        private readonly object my_property = new object();
        public object MyProperty { get { return my_property; } }
    }
    

З урахуванням усіх цих помилок FxCop, які кажуть, що я не повинен мати відкритих змінних-членів, здається, що другий варіант - це правильний спосіб це зробити. Правильно?

Чи є в цьому випадку якась різниця між властивістю get only та членом лише для читання?

Буду вдячний за будь-які коментарі / поради / тощо.


31
Іноді я хочу, щоб синтаксис автоматичного властивості містив get; readonly set;опцію.
Ден Брайант,


@DanBryant Є get; private set;, принаймні.
Marc.2377

2
@ Marc.2377, Власне, вони нещодавно додали підтримку {get;}, яка вирішує цю проблему.
Ден Брайант

@DanBryant Ах, справді. Я мав би прочитати відповіді спочатку;)
Marc.2377

Відповіді:


51

Версія:
Я думаю, це не має великого значення, якщо вас цікавить лише сумісність джерел.
Використання властивості краще для двійкової сумісності, оскільки ви можете замінити його властивістю, яка має сеттер, не порушуючи скомпільований код залежно від вашої бібліотеки.

Конвенція:
Ви дотримуєтесь конвенції. У таких випадках, коли відмінності між двома можливостями порівняно незначні, дотримуючись конвенції, краще. Одним з випадків, коли це може повернутися до вас, вас може вкусити - це код на основі рефлексії. Він може приймати лише властивості, а не поля, наприклад редактор властивостей / переглядач.

Серіалізація
Перехід від поля до властивості, ймовірно, призведе до поломки багатьох серіалізаторів. А AFAIK XmlSerializerробить лише серіалізацію державного майна, а не загальнодоступних полів.

Використання автовластивості
Ще однією поширеною варіацією є використання автовластивості з приватним установщиком. Незважаючи на те, що це коротко і властивість, воно не гарантує лише готовність до читання. Тому я віддаю перевагу іншим.

Поле для читання - це самодокументування.
Однак є одна перевага поля:
воно дає зрозуміти з першого погляду на загальнодоступний інтерфейс, що воно насправді незмінне (заборона відображення). Тоді як у випадку з властивістю ви можете бачити лише те, що не можете змінити його, тому вам доведеться звернутися до документації або реалізації.

Але чесно кажучи, я досить часто використовую перший у коді програми, оскільки я лінивий. У бібліотеках я, як правило, більш ретельний і дотримуюсь домовленостей.

C # 6.0 додає автоматичні властивості лише для читання

public object MyProperty { get; }

Отже, коли вам не потрібно підтримувати старі компілятори, ви можете мати справді властивість лише для читання з кодом, який такий же лаконічний, як поле для читання.


1
На даний момент я бажаю, щоб C # 9 додалиpublic type prop => get;
NetMage

66

Другий спосіб - кращий варіант.

private readonly int MyVal = 5;

public int MyProp { get { return MyVal;}  }

Це забезпечить MyValприсвоєння лише при ініціалізації (його також можна встановити в конструкторі).

Як ви вже зазначали - таким чином ви не викриваєте внутрішнього члена, дозволяючи змінити внутрішню реалізацію в майбутньому.


Дякую. перший варіант також забезпечує те саме. як ви вважаєте, в чому причина, чому цей варіант є кращим?
akonsu

Чи є якась причина, яку ми не повинні використовувати public int MyProp { get; private set; }? Я знаю, що це не по-справжньому лише для читання, але це досить проклято.
Mike Hofer

3
@Mike Hofer - Оскільки int оголошено лише для читання , ви не можете змінити його поза конструктором.
Почато

5
@Mike Hofer - це насправді залежить від того, в чому полягає намір ... Версія, яку я написав, відкриває внутрішній член, значення якого не може змінюватися після ініціалізації. Ваше відкриває учасника, значення якого може змінитися після ініціалізації. Дійсно залежить від того, що ви хочете ... Моє лише для читання, як і в, взагалі не може бути змінене після init, ваше лише для читання, як і в будь-якому зовнішньому класі.
Одін

1
@Oded - я можу це прийняти. Це тонка, але важлива різниця. Я бачу, де було б корисно, якби ви хотіли виставити властивість, яка вела себе як константа як усередині, так і зовні. Мої, звичайно, цього не робили б.
Mike Hofer

49

З введенням C # 6 (у VS 2015), тепер ви можете мати лише getавтоматичні властивості, в яких є неявне поле резервного копіювання readonly(тобто значення можуть бути призначені в конструкторі, але не в інших місцях):

public string Name { get; }

public Customer(string name)  // Constructor
{
    Name = name;
}

private void SomeFunction()
{
    Name = "Something Else";  // Compile-time error
}

І тепер ви також можете ініціалізувати властивості (із або без встановлення) вбудовано:

public string Name { get; } = "Boris";

Повертаючись до питання, це дає вам переваги варіанту 2 (публічний член є власністю, а не полем) із стислістю варіанту 1.

На жаль, це не забезпечує гарантії незмінності на рівні загальнодоступного інтерфейсу (як у пункті @ CodesInChaos про самодокументування), оскільки для споживача класу відсутність сетера не відрізняється від приватного сеттера.


Хороша інформація про новий синтаксис, однак, ви можете відобразити та активувати приватний сеттер та / або завантажити значення в поле резервного копіювання (незалежно від модифікаторів доступу.) Також можна розрізнити приватний сеттер та відсутність сеттера під час запуску -час використання рефлексії.
Шон Вільсон

1
@Shaun: гарна думка - можна багато речей зробити, розмірковуючи! Ряд можливостей, ймовірно, суперечить задуму оригінального програміста або навіть дизайнерів мов, але чи ви кажете, що readonlyполя можна змінювати за допомогою рефлексії (я не знаю відповіді, але це здається проблематичним)?
Боб Саммерс,

2
Я не говорив цього зокрема, ні, але відповідь: так .. ти можеш. gist.github.com/wilson0x4d/a053c0fd57892d357b2c Якщо ви вважаєте, що це проблематично, просто почекайте, поки ви дізнаєтесь, що кожна операційна система на планеті має механізм, за допомогою якого один процес може читати / писати пам'ять будь-якого іншого процесу (з урахуванням достатнього привілею виконання, саме тому.) Ось чому жодна система, що базується на програмному забезпеченні, ніколи не може бути по-справжньому безпечною, але, відвертаюся, це не має великого відношення до оригінального запитання :), навіть якщо це цікаво!
Шон Вільсон

Я б рекомендував прочитати детальну статтю, а також статтю
Білла Вагнера

13

Ви можете зробити це:

public int Property { get { ... } private set { ... } }

8
Так, ви можете, але за допомогою цієї техніки ви лише гарантуєте, що властивість не може бути змінена споживачами класу, а не те, що воно залишатиметься незмінним протягом усього життя об’єкта.
Боб Саммерс

Боб: Якщо властивість не може бути змінена споживачами класу, тоді властивість "readOnly", чи не так?
El Bayames

@ElBayames Ви не можете змінити посилання, але ви можете змінити внутрішні елементи. Подумайте, коли ви виставляєте об’єкт із внутрішнім станом. Ви можете просто отримати об'єкт за допомогою методу викритого get, а потім змінити внутрішні властивості цього об'єкта. Це не справжнє читання.
Matthew S

5

Я згоден, що другий шлях є кращим. Єдиною реальною причиною такої переваги є загальна перевага щодо того, що класи .NET не мають загальнодоступних полів. Однак, якщо це поле читається лише для читання, я не бачу, як би існували реальні заперечення, крім відсутності узгодженості з іншими властивостями. Справжня різниця між полем тільки для читання та властивістю "тільки для отримання" полягає в тому, що поле "лише для читання" забезпечує гарантію того, що його значення не зміниться протягом життя об'єкта, а властивість "лише для читання" - ні.


4

Другий спосіб є кращим через інкапсуляцію. Звичайно, поле readonly може бути загальнодоступним, але це суперечить ідіомам C #, у яких доступ до даних відбувається через властивості, а не поля.

Причиною цього є те, що властивість визначає загальнодоступний інтерфейс, і якщо реалізація резервної копії до цієї властивості змінюється, ви не закінчите розбивати решту коду, оскільки реалізація прихована за інтерфейсом.



1

У C # 9 Microsoft представить новий спосіб встановлення властивостей лише під час ініціалізації за допомогою init;методу приблизно так:

public class Person
{
  public string firstName { get; init; }
  public string lastName { get; init; }
}

Таким чином, ви можете призначити значення при ініціалізації нового об’єкта:

var person = new Person
{
  firstname = "John",
  lastName = "Doe"
}

Але пізніше ви не зможете змінити це:

person.lastName = "Denver"; // throws a compiler error
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.