Почему невозможно переопределить свойство, предназначенное только для получения, и добавить установщик? [закрыто]


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' не имеет переопределяемого средства доступа к set


9
StackOverflow имеет ограниченные возможности отвечать на вопросы от имени Microsoft. Попробуйте перефразировать свой вопрос.
Джей Базузи

1
Кстати, простое средство от этого раздражающего поведения в .net - иметь невиртуальное свойство только для чтения, Fooкоторое ничего не делает, кроме как обертывать защищенный абстрактный GetFooметод. Абстрактный производный тип может затенять свойство с помощью невиртуального свойства чтения-записи, которое ничего не делает, кроме как обертывать вышеупомянутый GetFooметод вместе с абстрактным SetFooметодом; конкретный производный тип мог бы делать то же самое, но предоставлять определения для GetFooи SetFoo.
supercat

1
Чтобы подлить масла в огонь, C ++ / CLI позволяет это.
ymett

1
Возможность добавления средства доступа при переопределении свойства была предложена в качестве изменения для будущей версии C # ( github.com/dotnet/roslyn/issues/9482 ).
Brandon Bonds

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

Ответы:


-4

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

Я с Microsoft по этому вопросу.
Скажем, я новый программист, которому было велено кодировать против производного базового класса. Я пишу что-то, что предполагает, что Bar не может быть записан (поскольку базовый класс явно указывает, что это свойство только для получения). Теперь с вашим выводом мой код может сломаться. например

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 не может быть изменен. Но если установка была возможна в производной, этот класс мог бы обслуживать устаревшие значения, если бы кто-то изменил свойство Bar объекта _source извне. Суть в том « Be Open, не делать хитрые вещи и удивительные люди »

Обновление : Илья Рыженков спрашивает: «Почему же тогда интерфейсы не работают по одним правилам?» Хм .., когда я думаю об этом, все становится еще грязнее.
Интерфейс - это контракт, в котором говорится: «Ожидайте, что у реализации будет свойство чтения с именем Bar». Лично у меня гораздо меньше шансов сделать это предположение о доступе только для чтения, если я увидел интерфейс. Когда я вижу свойство только для получения в интерфейсе, я читаю его как «Любая реализация предоставит этот атрибут Bar» ... в базовом классе он щелкает как «Bar - свойство только для чтения». Конечно, технически вы не нарушаете контракт ... вы делаете больше. Так что в каком-то смысле вы правы ... В заключение я бы сказал: «Сделайте так, чтобы недопонимание возникло как можно сильнее».


94
Я до сих пор не уверен. Даже если нет явного установщика, это не гарантирует, что свойство всегда будет возвращать один и тот же объект - его все равно можно изменить каким-либо другим методом.
ripper234

34
Разве тогда не должно быть то же самое и с интерфейсами? У вас может быть свойство readonly в интерфейсе и чтение / запись в типе реализации.
Илья Рыженков

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

22
Глядя на геттеры и сеттеры как на методы, я не понимаю, почему это должно быть «нарушением контракта» больше, чем добавление нового метода. Контракт базового класса не имеет ничего общего с тем, какой функциональности нет , а только с тем, что есть .
Dave Cousineau

37
-1 Я не согласен с таким ответом и считаю его вводящим в заблуждение. Поскольку базовый класс определяет поведение, которому должен соответствовать любой класс (см. Принцип замены Лискова), но не (и не должен) ограничивать добавление поведения. Базовый класс может только определять то, каким должно быть поведение, и он не может (и не должен) указывать, каким поведением «не должно быть» (т.е. «не должно быть доступно для записи на конкретных уровнях)».
Марсель Вальдес Ороско

40

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

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

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

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

Я думаю, что следующий синтаксис может подойти, но, как постоянно повторяет Эрик Липперт, реализация даже такой незначительной функции, как эта, по-прежнему требует больших усилий ...

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

или, для автореализуемых свойств,

public int MyProperty { override get; set; }

К сожалению, это не сработает, если ваш класс реализует интерфейс. В этом случае вы получите предупреждение компилятора CS0106 sayig The modifier 'override' is not valid for this item.
Роальд

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 вместо override, это просто не работает, и даже если бы оно было, оно не дало бы вам желаемого результата, а именно виртуального получателя, как описано в интерфейсе.


3
Или, в моем случае, {получить; частный набор; }. Я не нарушаю никаких контрактов, поскольку набор остается закрытым и касается только производного класса.
Machtyn

18

Возможно

tl; dr - Вы можете переопределить метод только для получения с помощью установщика, если хотите. В основном это просто:

  1. Создайте newсвойство с одинаковыми именами a getи a set.

  2. Если вы больше ничего не сделаете, старый getметод все равно будет вызываться при вызове производного класса через его базовый тип. Чтобы исправить это, добавьте abstractпромежуточный уровень, который использует overrideстарый getметод, чтобы заставить его возвращать результат нового getметода.

Это позволяет нам переопределять свойства с помощью get/, setдаже если они отсутствуют в их базовом определении.

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

  • Если базовое определение было get-only, вы можете использовать более производный тип возвращаемого значения.

  • Если базовое определение было set-only, то вы можете использовать менее производный тип возврата.

  • Если базовое определение уже было 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; } }
}

Проблема: Не удается -только с /overridegetgetset

Вы хотите 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свойство -only; вместо этого вы заменяете его с помощью 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-Только, set-только или get-и- setсобственности, независимо от того, каким он был в базовом классе.

  2. Измените тип возвращаемого значения метода в производных классах.

Основные недостатки заключаются в том, что необходимо выполнить больше кода и добавить лишнее abstract classв дереве наследования. Это может немного раздражать конструкторов, которые принимают параметры, потому что их нужно копировать / вставлять на промежуточный уровень.


Задал свой собственный вопрос: stackoverflow.com/questions/22210971
Nat

Хороший подход (+1). Однако я сомневаюсь, что это будет работать гладко с entity framework.
Майк де Клерк

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
Привет, суперкар. Вы правы в отношении наблюдаемых извне побочных эффектов, препятствующих тому, чтобы функция была чистой, но другое ограничение заключается в том, что значение результата чистой функции должно зависеть только от параметров. Таким образом, чистое статическое свойство как функция должно быть неизменным и постоянным. Сложный компилятор может заменить все последующие вызовы чистой функции без параметров результатом, возвращаемым первым вызовом.
T.Tobler

Вы правы, что моя терминология была неточной, но я думаю, что уместность того, что что-то является свойством только для чтения, а не методом, зависит от отсутствия побочных эффектов, а не от чистоты.
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 {...} не допускается. Затем вы можете просто создать обычную панель свойств с функциями get и set, даже если свойство Bar из интерфейса не требует набора.
Ибрагим бен Салах

1

Я могу понять все, что вы говорите, но на самом деле автоматические свойства C # 3.0 в этом случае становятся бесполезными.

Ничего подобного сделать нельзя:

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

IMO, C # не должен ограничивать такие сценарии. Разработчик обязан использовать его соответствующим образом.


Я не понимаю твою точку зрения. Вы согласны с тем, что C # должен позволять добавлять сеттер, или вы согласны с большинством других людей, ответивших на этот вопрос?
ripper234

2
Как я уже сказал, IMO, C # должен позволять добавлять сеттер. Разработчик обязан использовать его соответствующим образом.
Thomas Danecker

1

Проблема в том, что по какой-то причине Microsoft решила, что должно быть три различных типа свойств: только для чтения, только для записи и для чтения-записи, только одно из которых может существовать с данной подписью в данном контексте; свойства могут быть переопределены только идентично объявленными свойствами. Чтобы сделать то, что вы хотите, необходимо создать два свойства с одинаковым именем и сигнатурой, одно из которых предназначено только для чтения, а другое - для чтения и записи.

Лично мне хотелось бы, чтобы вся концепция «свойств» была упразднена, за исключением того, что синтаксис свойств можно использовать как синтаксический сахар для вызова методов «get» и «set». Это не только упростит использование опции «добавить набор», но также позволит «get» вернуть тип, отличный от «set». Хотя такая возможность не будет использоваться очень часто, иногда может быть полезно, чтобы метод get возвращал объект-оболочку, в то время как «set» мог принимать либо оболочку, либо фактические данные.


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
Вы уверены, что можете вызвать сеттер через отражение, которого не существует (как это происходит в описанных свойствах только для геттера)?
OR 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 не обязана позволять вам делать то, что вы хотите. Причина в том, что сеттер существует в той или иной форме, и вы можете легко выполнить то, что хотите, Veerryy .

Базовый класс или любой класс, с помощью которого вы можете читать свойство {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потому что вы не хотите, чтобы базовый класс обрабатывал его, у вас нет доступа для проверки того, как он его обрабатывает, или вам было поручено очень быстро взломать какой-то код против вашей воли .

Как в последнем, так и в прошлом (Заключение)

Вкратце: 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 в абстракции, если потомок в любом случае имеет полный контроль над своими частными полями?
Suamere

+1: Этот вопрос о том, почему Microsoft не позволяет вам разорвать контракт. Хотя причина ваших отрицательных голосов больше связана с тем, почему Microsoft позволяет контракту включать только свойства только для чтения. Итак, этот ответ верен в контексте.
Suamere

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

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

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

Это феноменально опасно, потому что следующий результат возвращает разные результаты, несмотря на то, что объект является одним и тем же: Test t = new Test(); Base b = t; Console.WriteLine(t.BaseProperty)дает вам 5, Console.WriteLine(b.BaseProperty)дает вам 0, и когда ваш преемник должен отладить это, вам лучше уйти далеко-далеко.
Mark Sowul

Я согласен с Марком, это очень противно. Если бы обе реализации BaseProperty были поддержаны одной и той же переменной, все было бы хорошо, хотя ИМХО. IE: сделайте _baseProperty защищенным и отмените _testBaseProperty. Кроме того, AFAIK реализация в Base не обязательно должна быть виртуальной, поскольку вы фактически не переопределяете ее.
Steazy

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

-1

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

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

Однако из правил часто бывают исключения. Вполне возможно, что, например, в одном производном классе контекст сужает возможные значения перечисления до 3 из 10, но пользователю этого объекта все равно нужно решить, какое из них правильное. Производный класс должен делегировать ответственность за определение значения пользователю этого объекта. Важно понимать , что пользователь этого объекта должен хорошо знать об этом исключении и брать на себя ответственность за установку правильного значения.

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

Это также подтверждает правильное замечание Гишу: «Сделайте так, чтобы недопонимание было как можно сложнее».


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

Дополнительный установщик может изменить частную переменную в классе. Так называемое свойство «только для чтения» из базового класса все равно будет определять значение «изнутри класса» (путем чтения новой частной переменной). Я не вижу здесь проблемы. Также посмотрите, например, что List<T>.Countделает: он не может быть назначен, но это не означает, что он доступен только для чтения в том смысле, что он не может быть изменен пользователями класса. Его вполне можно изменить, хотя и косвенно, путем добавления и удаления элементов списка.
OR Mapper

-2

Потому что на уровне IL свойство чтения / записи преобразуется в два метода (получатель и установщик).

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

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


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

@ ripper234 См. ответ Мэтта Болта: если вы хотите сделать новое свойство видимым только для пользователей производного класса, вы должны использовать newвместо override.
Дэн Бериндей

1
Это не остается скрытым. Вы как будто говорите, что не можете добавить новый метод в дочерний класс, потому что новый метод будет «оставаться скрытым от внешнего мира». Конечно, он остается скрытым с точки зрения базового класса, но доступен для клиентов дочернего класса. (То есть, чтобы повлиять на то, что должен возвращать геттер)
Sheepy

Шипи, я совсем не это говорю. Я имею в виду, что именно так, как вы указали, он останется скрытым «с точки зрения базового класса». Вот почему я добавил «что касается интерфейса ваших классов ». Если вы создаете подкласс базового класса, почти наверняка «внешний мир» будет ссылаться на экземпляры вашего объекта с типом базового класса. Если бы они напрямую использовали ваш конкретный класс, вам не потребовалась бы совместимость с базовым классом, и вы могли бы во что бы то ни стало использовать newсвои дополнения.
Ishmaeel

-2

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


Голосование за. Это очень лаконично. Почему Microsoft должна позволять потребителю базового класса разрывать контракт? Я согласен с этой очень краткой версией моего слишком длинного и запутанного ответа. Лол. Если вы являетесь потребителем базового класса, вам не следует разрывать контракт, но вы МОЖЕТЕ реализовать его несколькими разными способами.
Suamere
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.