Чому неможливо замінити властивість лише getter та додати сетер? [зачинено]


141

Чому такий код C # не дозволений:

public abstract class BaseClass
{
    public abstract int Bar { get;}
}

public class ConcreteClass : BaseClass
{
    public override int Bar
    {
        get { return 0; }
        set {}
    }
}

CS0546 'ConcreteClass.Bar.set': неможливо переосмислити, оскільки 'BaseClass.Bar' не має доступного набору доступу


9
StackOverflow має обмежені можливості відповідати на запитання від імені Microsoft. Подумайте перефразовувати своє запитання.
Джей Базузі,

1
До речі, простий засіб для цієї дратівливої ​​поведінки в .net полягає в тому, щоб мати невіртуальне властивість лише для читання, Fooяке не робить нічого, крім обгортання захищеного абстрактного GetFooметоду. Абстрактний похідний тип може затінювати властивість не-віртуальною властивістю читання-запису, яка не робить нічого, крім обгортання згаданого GetFooметоду разом із абстрактним SetFooметодом; конкретний похідний тип міг би зробити вищесказане, але визначити пропозиції для GetFooта SetFoo.
supercat

1
Просто, щоб додати паливо до вогню, C ++ / CLI це дозволяє.
ymett

1
Можливість додавати аксесуар під час зміни властивості запропонована як зміна для майбутньої версії C # ( github.com/dotnet/roslyn/isissue/9482 ).
Брендон Бонд

1
Якщо чесно, це питання часто виникає, коли виникає потреба розширити існуючий тип бібліотеки, який використовує таку схему: базовий клас лише для читання, похідний мутаційний клас. Оскільки бібліотека вже використовує поганий зразок (наприклад, WPF), виникає необхідність вирішити цю погану схему. Лекція не врятує когось від необхідності виконувати вирішення в кінці дня.
rwong

Відповіді:


-4

Тому що письменник Baseclass прямо заявив, що Bar має бути властивістю лише для читання. Немає сенсу деривативам розірвати цей договір і змусити його читати-писати.

Я з Microsoft на цьому.
Скажімо, я новий програміст, якому сказали кодувати проти виведення Baseclass. я пишу щось, що передбачає, що бар не може бути записаний (оскільки Baseclass прямо заявляє, що це лише властивість отримати). Тепер з вашим виведенням мій код може зламатися. напр

public class BarProvider
{ BaseClass _source;
  Bar _currentBar;

  public void setSource(BaseClass b)
  {
    _source = b;
    _currentBar = b.Bar;
  }

  public Bar getBar()
  { return _currentBar;  }
}

Оскільки Bar не можна встановити відповідно до інтерфейсу BaseClass, BarProvider припускає, що кешування - це безпечна річ - оскільки Bar не можна змінювати. Але якщо встановлення було можливим у виведенні, цей клас міг би слугувати несвіжими значеннями, якщо хтось змінив властивість бар об'єкта _source зовнішньо. Справа в тому, щоб бути « відкритими, уникати підступних речей і дивувати людей »

Оновлення : Ілля Риженков запитує "Чому інтерфейси тоді не грають за тими ж правилами?" Хм .. це стає бруднішим, як я думаю про це.
Інтерфейс - це договір, в якому сказано, що "очікуйте, що реалізація матиме властивість для читання з назвою Bar". Особисто я набагато менше шансів зробити таке припущення лише для читання, якщо побачив інтерфейс. Коли я бачу властивість для отримання лише інтерфейсу, я читаю його як "Будь-яка реалізація викриє цей атрибут Bar" ... у базовому класі він натискає як "Бар є властивістю лише для читання". Звичайно технічно ти не порушуєш договір .. ти робиш більше. Отже, ви маєте рацію в певному сенсі .. Я б закрив, сказавши: "зробіть це якомога важче, щоб непорозуміння виникали".


94
Я досі не впевнений. Навіть якщо немає явного сеттера, це не гарантує, що властивість завжди поверне той самий об’єкт - його все одно можна змінити іншим методом.
ripper234

34
Чи не повинно те ж саме бути і для інтерфейсів? Ви можете мати властивість лише для читання в інтерфейсі та читати / записувати на тип реалізації.
Ілля Риженков

49
Я не згоден: базовий клас лише оголосив властивість такою, що не має сеттера; це не те саме, що властивість лише для читання. "Тільки для читання" - це лише значення, яке ви йому присвоюєте. Жодних гарантій, вбудованих у C #, взагалі немає. Ви можете мати властивість лише отримання, яке змінюється щоразу, або властивість get / set, куди сетер кидає InvalidOperationException("This instance is read-only").
Роман Старков

21
Розглядаючи геттерів та сеттерів як методи, я не бачу, чому це має бути "порушення договору" більше, ніж додавання нового методу. Договір базового класу не має нічого спільного з тим, що функціональність НЕ присутній, тільки з тим, що в даний час .
Дейв Кузен

37
-1 Я не згоден з цією відповіддю і вважаю її помилковою. Оскільки базовий клас визначає поведінку, якої повинен дотримуватися будь-який клас (Див. Принцип заміщення Ліскова), але не (і не повинен) обмежувати додавання поведінки. Базовий клас може визначати лише те, якою має бути поведінка, і він не може (і не повинен) визначати, якою поведінка "не повинна бути" (тобто "не повинна бути написана на конкретних рівнях)".
Marcel Valdez Orozco

39

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

public override int MyProperty { get { ... } set { ... } }

Цілком явно, що і перевірки, і getі set. У setбазовому класі немає, тому компілятор скаржиться. Так само, як ви не можете замінити метод, який не визначений у базовому класі, ви також не зможете замінити сетер.

Ви можете сказати, що компілятор повинен вгадати ваш намір і застосувати лише заміщення до методу, який можна перекрити (тобто в цьому випадку getter), але це суперечить одному з принципів дизайну C # - що компілятор не повинен вгадувати ваші наміри , тому що ви можете здогадатися неправильно, не знаючи.

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

public int MyProperty
{
    override get { ... }
    set { ... }
}

або, для властивостей, що реалізуються автоматично,

public int MyProperty { override get; set; }

19

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

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

Далі - спрощений приклад того, що мені було потрібно сьогодні. Мені в кінцевому підсумку довелося додати сетер у мій інтерфейс, просто щоб обійти це. Причиною додавання сеттера і не додаванням, скажімо, методу SetProp є те, що одна конкретна реалізація інтерфейсу використовувала DataContract / DataMember для серіалізації Prop, що було б зайво ускладнено, якби мені довелося додати інше властивість лише для цієї мети серіалізації.

interface ITest
{
    // Other stuff
    string Prop { get; }
}

// Implements other stuff
abstract class ATest : ITest
{
    abstract public string Prop { get; }
}

// This implementation of ITest needs the user to set the value of Prop
class BTest : ATest
{
    string foo = "BTest";
    public override string Prop
    {
        get { return foo; }
        set { foo = value; } // Not allowed. 'BTest.Prop.set': cannot override because 'ATest.Prop' does not have an overridable set accessor
    }
}

// This implementation of ITest generates the value for Prop itself
class CTest : ATest
{
    string foo = "CTest";
    public override string Prop
    {
        get { return foo; }
        // set; // Not needed
    }
}

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

Також згадка про використання new замість переопределення тут не поширюється, вона просто не працює, і навіть якби це зробило, це не дасть вам бажаного результату, а саме віртуального геттера, як описано в інтерфейсі.


2
Або в моєму випадку {get; приватний набір; }. Я не порушую жодного контракту, оскільки набір залишається приватним і стосується лише класу похідних.
Махтін

16

Це можливо

tl; dr - Якщо ви хочете, ви можете замінити метод отримання лише за допомогою сеттера. Це в основному просто:

  1. Створіть newвластивість, яка має і а, getі setвживання, використовуючи те саме ім'я.

  2. Якщо ви нічого не робите, то старий getметод все одно буде називатися, коли похідний клас буде викликаний через його базовий тип. Щоб виправити це, додайтеabstract проміжний шар, який використовує overrideстарий getметод, щоб змусити його повернути результат нового getметоду.

Це дає нам змогу переосмислити властивості за допомогою get/ setнавіть, якщо їх у базовому визначенні не вистачало.

Як бонус, ви також можете змінити тип повернення, якщо хочете.

  • Якщо базовим було визначення get -лише, тоді можна використовувати більш похідний тип повернення.

  • Якщо базовим було визначення set -лише, тоді ви можете отримати менш похідний тип повернення.

  • Якщо базове визначення вже було get/set , то:

    • ви можете використовувати більш похідний тип повернення, якщо ви робите це set-лише;

    • ви можете використовувати менш похідний тип повернення, якщо ви робите це get- тільки.

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

Ситуація: Попередня get власність

У вас є структура класу, яку ви не можете змінити. Можливо, це лише один клас, або це вже існуюче дерево спадкування. У будь-якому випадку ви хочете додати setметод до властивості, але не можете.

public abstract class A                     // Pre-existing class; can't modify
{
    public abstract int X { get; }          // You want a setter, but can't add it.
}
public class B : A                          // Pre-existing class; can't modify
{
    public override int X { get { return 0; } }
}

Проблема: Неможливо- overrideтільки getзget /set

Ви хочете мати overrideз get/ setвластивістю, але він не буде компілюватися.

public class C : B
{
    private int _x;
    public override int X
    {
        get { return _x; }
        set { _x = value; }   //  Won't compile
    }
}

Рішення: Використовуйте abstract проміжний шар

Хоча ви не можете безпосередньо overrideз власністю get/ set, ви можете :

  1. Створіть new get/ setз власним іменем /.

  2. overrideстарий getметод з аксесуаром до нового getметоду для забезпечення узгодженості.

Отже, спочатку ви пишете abstractпроміжний шар:

public abstract class C : B
{
    //  Seal off the old getter.  From now on, its only job
    //  is to alias the new getter in the base classes.
    public sealed override int X { get { return this.XGetter; }  }
    protected abstract int XGetter { get; }
}

Потім ви пишете клас, який не збирався б раніше. Це буде компілювати на цей раз , тому що ви на самому ділі не override«ІНГ в get-Тільки власності; натомість ви замінюєте його за допомогою newключового слова.

public class D : C
{
    private int _x;
    public new virtual int X { get { return this._x; } set { this._x = value; } }

    //  Ensure base classes (A,B,C) use the new get method.
    protected sealed override int XGetter { get { return this.X; } }
}

Результат: Все працює!

Очевидно, це працює як задумано D.

var test = new D();
Print(test.X);      // Prints "0", the default value of an int.

test.X = 7;
Print(test.X);      // Prints "7", as intended.

Все ще працює за призначенням, якщо розглядати його Dяк один із його базових класів, наприклад, Aабо B. Але причина, чому це працює, може бути трохи менш очевидною.

var test = new D() as B;
//test.X = 7;       // This won't compile, because test looks like a B,
                    // and B still doesn't provide a visible setter.

Однак визначення базового класу getдосі остаточно переосмислене визначенням похідного класу get, тому воно все ще повністю узгоджується.

var test = new D();
Print(test.X);      // Prints "0", the default value of an int.

var baseTest = test as A;
Print(test.X);      // Prints "7", as intended.

Обговорення

Цей метод дозволяє додавати setметоди до get-тільки властивостей. Ви також можете використовувати його, щоб робити такі речі, як:

  1. Змініть будь-яку властивість на властивість get-only, set-only або get-and- set, незалежно від того, що вона була в базовому класі.

  2. Змініть тип повернення методу у похідних класах.

Основні недоліки полягають у тому, що abstract classв дереві спадкування є більше кодування і додаткового . Це може трохи дратувати конструкторів, які приймають параметри, оскільки їх потрібно копіювати / вставляти в проміжний шар.


Поставив це власне питання: stackoverflow.com/questions/22210971
Nat

Гарний підхід (+1). Однак я сумніваюся, чи буде це справно працювати з сутнісними рамками.
Майк де Клерк

9

Я погоджуюся з тим, що неможливість перекриття геттера у похідному типі - це анти-модель. Лише для читання вказується на відсутність виконання, а не на договір чистого функціоналу (мається на увазі у відповіді голосування вгорі).

Я підозрюю, що Microsoft мала таке обмеження або тому, що було пропагувано те саме помилкове уявлення, або, можливо, через спрощення граматики; однак, тепер, коли цю область можна застосувати для отримання або встановлення окремо, можливо, ми можемо сподіватися, що переоцінка може бути занадто.

Помилкове уявлення, вказане у відповіді головного голосу, що властивість лише для читання має бути якось більш "чистою", ніж властивість читання / запису, є смішною. Просто подивіться на багато загальних властивостей лише для читання в рамках; значення не є постійним / чисто функціональним; наприклад, DateTime.Now - лише для читання, але нічого, крім чистого функціонального значення. Спроба «кешувати» значення властивості лише для читання, припускаючи, що наступного разу поверне те саме значення, є ризикованою.

У будь-якому випадку я використав одну з наступних стратегій для подолання цього обмеження; обидва є менш ніж досконалими, але дозволять вам кульгати за межі цього мовного дефіциту:

   class BaseType
   {
      public virtual T LastRequest { get {...} }
   }

   class DerivedTypeStrategy1
   {
      /// get or set the value returned by the LastRequest property.
      public bool T LastRequestValue { get; set; }

      public override T LastRequest { get { return LastRequestValue; } }
   }

   class DerivedTypeStrategy2
   {
      /// set the value returned by the LastRequest property.
      public bool SetLastRequest( T value ) { this._x = value; }

      public override T LastRequest { get { return _x; } }

      private bool _x;
   }

DateTime.Now є суто функціональним, оскільки він не має побічних ефектів (той факт, що потрібен час для виконання, можливо, можна назвати побічним ефектом, але оскільки час виконання - принаймні при обмеженому та короткому - не вважається побічним ефектом будь-якого іншого методу, я також не вважав би його таким.
supercat

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

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

2

Можливо, ви можете подолати проблему, створивши новий ресурс:

public new int Bar 
{            
    get { return 0; }
    set {}        
}

int IBase.Bar { 
  get { return Bar; }
}

4
Але ви можете це зробити лише при реалізації інтерфейсу, а не при успадкуванні від базового класу. Вам доведеться вибрати інше ім’я.
svick

Я припускаю, що приклад класу реалізує IBase (щось на зразок загальнодоступного інтерфейсу IBase {int Bar {get;}}), інакше при впровадженні класу int IBase.Bar {...} заборонено. Тоді ви можете просто створити звичайну панель властивостей як з отриманням, так і з встановленням, якщо властивість Bar в інтерфейсі не потребує набору.
Ібрагім бен Салах

1

Я можу зрозуміти всі ваші моменти, але фактично автоматичні властивості C # 3.0 в цьому випадку стають марними.

Ви не можете зробити щось подібне:

public class ConcreteClass : BaseClass
{
    public override int Bar
    {
        get;
        private set;
    }
}

IMO, C # не повинні обмежувати такі сценарії. Розробник повинен нести відповідне використання.


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

2
Як я вже сказав, IMO, C # повинні дозволяти додавати сетер. Розробник повинен нести відповідне використання.
Томас Данекер

1

Проблема полягає в тому, що з будь-якої причини Microsoft вирішила, що існує три різних властивості: лише для читання, лише для читання та запису, лише один з яких може існувати із заданим підписом у заданому контексті; властивості можуть бути замінені лише однаково оголошеними властивостями. Щоб зробити те, що ви хочете, необхідно створити два властивості з однаковим іменем та підписом - один з яких був лише для читання, а один - для читання-запису.

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


0

Ось обхід для досягнення цього за допомогою Reflection:

var UpdatedGiftItem = // object value to update;

foreach (var proInfo in UpdatedGiftItem.GetType().GetProperties())
{
    var updatedValue = proInfo.GetValue(UpdatedGiftItem, null);
    var targetpropInfo = this.GiftItem.GetType().GetProperty(proInfo.Name);
    targetpropInfo.SetValue(this.GiftItem, updatedValue,null);
}

Таким чином ми можемо встановити значення об'єкта на властивості, що читається тільки. Хоча це може не працювати у всіх сценаріях!


1
Ви впевнені, що можете зателефонувати сеттеру за допомогою відображення, яке не існує (як це відбувається в описаних властивостях лише для отримання)?
АБО Mapper

0

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


Якщо перша (переосмислює абстрактну властивість)

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

public int GetBar(){}

Але якщо у вас немає контролю над цим, а функціональність базового класу зчитується з його власного загальнодоступного властивості (дивно), то просто зробіть це:

public abstract class BaseClass
{
    public abstract int Bar { get; }
}

public class ConcreteClass : BaseClass
{
    private int _bar;
    public override int Bar
    {
        get { return _bar; }
    }
    public void SetBar(int value)
    {
        _bar = value;
    }
}

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

public abstract class BaseClass {
    protected int _bar;
    public int Bar { get { return _bar; } }
    protected void DoBaseStuff()
    {
        SetBar();
        //Do something with _bar;
    }
    protected abstract void SetBar();
}

public class ConcreteClass : BaseClass {
    protected override void SetBar() { _bar = 5; }
}

В останньому випадку (перевизначення ОТРИМАТИ тільки властивість класу за)

Кожна неабразована властивість має сетер. В іншому випадку він марний, і вам не варто дбати про його використання. Microsoft не повинен дозволяти вам робити те, що ви хочете. Причина: сеттер існує в тій чи іншій формі, і ви можете легко виконати те, що хочете, Вееррі .

Базовий клас або будь-який клас, з яким ви можете прочитати властивість {get;}, має ДЕЙЧИЙ вид виставленого сеттера для цього властивості. Метадані будуть виглядати приблизно так:

public abstract class BaseClass
{
    public int Bar { get; }
}

Але реалізація матиме два кінці спектру складності:

Найменший комплекс:

public abstract class BaseClass
{
    private int _bar;
    public int Bar { 
        get{
            return _bar;
        }}
    public void SetBar(int value) { _bar = value; }
}

Найскладніші:

public abstract class BaseClass
{
    private int _foo;
    private int _baz;
    private int _wtf;
    private int _kthx;
    private int _lawl;

    public int Bar
    {
        get { return _foo * _baz + _kthx; }
    }
    public bool TryDoSomethingBaz(MyEnum whatever, int input)
    {
        switch (whatever)
        {
            case MyEnum.lol:
                _baz = _lawl + input;
                return true;
            case MyEnum.wtf:
                _baz = _wtf * input;
                break;
        }
        return false;
    }
    public void TryBlowThingsUp(DateTime when)
    {
        //Some Crazy Madeup Code
        _kthx = DaysSinceEaster(when);
    }
    public int DaysSinceEaster(DateTime when)
    {
        return 2; //<-- calculations
    }
}
public enum MyEnum
{
    lol,
    wtf,
}

Моя думка бути, у будь-якому випадку, у вас сетер виставляється. У вашому випадку ви, можливо, захочете переосмислити, int Barоскільки ви не хочете, щоб базовий клас обробляв його, не маєте доступу до перегляду того, як він обробляє його, або ж перед ним було поставлено завдання присвоїти якийсь код справжній fast'n'dirty проти вашої волі .

І в минулому, і в попередньому (Висновок)

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


0

Рішення лише для невеликого підмножини випадків використання, але все-таки: у C # 6.0 сеттер "для читання" автоматично додається для перекритих властивостей лише для отримання.

public abstract class BaseClass
{
    public abstract int Bar { get; }
}

public class ConcreteClass : BaseClass
{
    public override int Bar { get; }

    public ConcreteClass(int bar)
    {
        Bar = bar;
    }
}

-1

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


1
-1: Наявність сетера не дозволяє автоматично нащадкові зламати інваріантів. Сеттер все одно може змінити лише речі, які вже змінилися нащадком.
Роман Старков

@RomanStarkov Це правда. Але питання цієї посади є; Чому не можна додати сетера до абстракції, який уклав контракт з вами. Ваш коментар був би доречним у зворотному запитанні; Чому Microsoft дозволяє вам створювати властивість ReadOnly в абстракції, якщо нащадок все-таки повністю контролює свої приватні поля?
Суамер

+1: Це питання стосується того, чому Microsoft не дозволяє розірвати контракт. Незважаючи на те, що причина ваших анонсів полягає в тому, чому Microsoft дозволяє договору включати лише властивості для читання. Тож ця відповідь правильна в контексті.
Суамер

@Suamere в договорі не зазначено, що властивість не може бути змінена. Все, про що йдеться, - це те, що існує властивість, яку можна прочитати. Додавання сетера не порушить цей договір. Ви можете інтерпретувати це як намір створити властивість лише для читання, але ви не можете гарантувати в C # 6, що немає можливості її змінити, отже, контракт, про який ви думаєте, насправді не існує. Подумайте про інтерфейс: він може запропонувати договір на майно для отримання (не лише отримання!). Цей інтерфейс можна добре реалізувати властивістю get + set.
Роман Старков

Ви підходите до цього з погляду конкретного автора. Він може змінити це, як заманеться. Я підходжу до цього з точки зору автора базового класу -> Підхід як споживач кінцевого результату класу-реалізатора. Автор базового класу не хоче, щоб будь-який споживач майбутніх конкретних класів міг змінити цю властивість без того, щоб автор конкретного класу викрив спеціальний метод обробки обставин для кожного конкретного випадку. Тому що в базовому класі це властивість трактується як незмінне, і про це передається лише викриття його як такого.
Суамер

-1

Це не неможливо. Вам просто потрібно використовувати "нове" ключове слово у власності. Наприклад,

namespace {
    public class Base {
        private int _baseProperty = 0;

        public virtual int BaseProperty {
            get {
                return _baseProperty;
            }
        }

    }

    public class Test : Base {
        private int _testBaseProperty = 5;

        public new int BaseProperty {
            get {
                return _testBaseProperty;
            }
            set {
                _testBaseProperty = value;
            }
        }
    }
}

Схоже, такий підхід задовольняє обидві сторони цієї дискусії. Використання "нового" розриває контракт між реалізацією базового класу та реалізацією підкласу. Це необхідно, коли у класу можуть бути декілька контрактів (через інтерфейс або базовий клас).

Сподіваюся, це допомагає


29
Ця відповідь неправильна, оскільки позначене властивість newбільше не перекриває віртуальну базову. Зокрема, якщо базова властивість абстрактна , як це є в початковій публікації, неможливо використовувати newключове слово у неабразованому підкласі, тому що ви повинні перекрити всі абстрактні члени.
Тімві

Це феноменально небезпечно, оскільки наступні повертають різні результати, незважаючи на те, що вони є одним і тим же об'єктом: Test t = new Test(); Base b = t; Console.WriteLine(t.BaseProperty)дає 5, Console.WriteLine(b.BaseProperty)дає 0, а коли ваш наступник повинен налагодити це, вам краще було б переїхати далеко-далеко.
Марк Совул

Я згоден з Марком, це надзвичайно неприємно. Якщо обидві реалізації BaseProperty були підкріплені однією і тією ж змінною, було б добре, якщо IMHO. IE: Зробити _baseProperty захищеним та скасувати _testBaseProperty. Крім того, AFAIK реалізація в Base не повинна бути віртуальною, оскільки ви насправді її не переосмислюєте.
Steazy

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

-1

Властивість лише для читання в базовому класі вказує, що ця властивість являє собою значення, яке завжди можна визначити з класу (наприклад, значення enum, що відповідає контексту (db-) об'єкта). Таким чином, відповідальність за визначення значення залишається всередині класу.

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

Однак правила часто мають винятки. Цілком можливо, що, наприклад, в одному похідному класі контекст звужує можливі значення перерахунку до 3 з 10, але користувачеві цього об'єкта все ж потрібно вирішити, який з них правильний. Похідному класу необхідно делегувати відповідальність визначення значення користувачеві цього об'єкта. Важливо усвідомити , що користувач цього об’єкта повинен добре знати цей виняток і брати на себе відповідальність за встановлення правильного значення.

Моїм рішенням у таких ситуаціях було б залишити властивість лише для читання та додати нове властивість для читання-запису до похідного класу для підтримки винятку. Перевизначення вихідної властивості просто поверне значення нової властивості. Нове властивість може мати власне ім'я, що вказує на контекст цього винятку належним чином.

Це також підтверджує обгрунтоване зауваження: "зробіть це якомога важче, щоб непорозуміння виникали" від Gishu.


1
Жодна незграбність не має на увазі ReadableMatrixклас (з двоаргументованою властивістю, що індексується лише для читання), що має підтипи ImmutableMatrixта MutableMatrix. Код, якому потрібно лише зчитувати матрицю, і не байдуже, чи це може змінитися якийсь час у майбутньому, може прийняти параметр типу ReadableMatrixта бути абсолютно задоволений змінними або незмінними підтипами.
supercat

Додатковий сеттер може змінювати приватну змінну в межах класу. Так зване властивість "лише для читання" з базового класу все одно визначатиме значення "зсередини класу" (зчитування нової приватної змінної). Я не бачу цього питання. Також подивіться, наприклад, що List<T>.Countце робить: це не може бути призначено, але це не означає, що воно "лише для читання", оскільки воно не може бути змінено користувачами класу. Це дуже добре може бути змінено, хоча і опосередковано, шляхом додавання та видалення елементів списку.
АБО Mapper

-2

Тому що на рівні IL властивість читання / запису перекладається на два (getter та setter) методи.

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

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


2
Мене мало хвилює "зовнішній світ". Я хочу додати функціональність класу, який завжди видно лише коду, який знає конкретний клас, а не базовий.
ripper234

@ ripper234 Дивіться відповідь Метта болта: якщо ви хочете зробити нове властивість видимим лише користувачам похідного класу, ви повинні використовувати його newзамість override.
Дан Беріндей

1
Це не залишається прихованим. Ви ніби говорите, що не можете додати новий метод до дочірнього класу, оскільки новий метод буде "залишатися прихованим від зовнішнього світу". Звичайно, це залишається прихованим з точки зору базового класу, але це доступно клієнтам дочірнього класу. (Тобто впливати на те, що повинен повернутися геттер)
Sheepy

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

-2

Тому що клас, який має властивість лише для читання (без сеттера), ймовірно, має для цього вагому причину. Наприклад, не може бути жодного базового сховища даних. Дозволяючи створити сетер розриває контракт, викладений класом. Це просто погано OOP.


Голосування Це дуже лаконічно. Чому Microsoft повинна дозволити споживачеві базового класу розірвати договір? Я погоджуюся з цією дуже короткою версією моєї занадто довгої і заплутаної відповіді. Лол. Якщо ви споживач базового класу, вам не слід розривати договір, але ви МОЖЕТЕ його реалізувати двома способами.
Суамер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.