Почему невозможно переопределить свойство getter-only и добавить сеттер?

Почему вы думаете (или, почему это хорошо), Microsoft решила не разрешать:

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" не имеет переопределяемого набора

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

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

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

public int MyProperty { override get; set; }

Ответ 3

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

Сначала я хотел бы утверждать, что наличие свойства get-only не обязательно переводится в режим только для чтения. Я интерпретирую его как "Из этого интерфейса /abtract вы можете получить это значение", это не означает, что для какой-либо реализации этого интерфейса/абстрактного класса не требуется, чтобы пользователь/программа задавал это значение явно. Абстрактные классы служат для реализации части необходимой функциональности. Я не вижу абсолютно никакой причины, по которой унаследованный класс не мог добавить сеттер, не нарушая никаких контрактов.

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

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

Ответ 4

Я согласен, что неспособность переопределить геттер в производном типе - это анти-шаблон. Только для чтения указывается на отсутствие реализации, а не на контракт чистого функционала (подразумевается верхним голосованием).

Я подозреваю, что 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;
   }

Ответ 5

Возможно

tl; dr - Вы можете переопределить метод get-only с помощью setter, если хотите. Это в основном просто:

  • Создайте свойство new, которое имеет как get, так и set с тем же именем.

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

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

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

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

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

  • Если базовое определение уже было get/set, то:

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

    • вы можете использовать менее производный тип возврата , если вы делаете его get - только.

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

Ситуация: ранее существовавшее get -одно свойство

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

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

Проблема: не может override с

Вы хотите 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, вы можете:

  • Создайте свойство new get/set с тем же именем.

  • 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 -only. Вы также можете использовать его для создания таких вещей, как:

  • Измените любое свойство в свойство get -only, set -only или get -and- set, независимо от того, что было в базовом классе.

  • Измените возвращаемый тип метода в производных классах.

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

Ответ 6

Возможно, вы столкнулись с проблемой, создав новое свойство:

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

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

Ответ 7

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

Вы не можете сделать ничего подобного:

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

IMO, С# не должен ограничивать такие сценарии. Это ответственность разработчика за его использование.

Ответ 8

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

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

Ответ 9

Ниже приведено обход, чтобы добиться этого с помощью 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);
}

Таким образом, мы можем установить значение объекта для свойства, которое является только для чтения. Возможно, это не работает во всех сценариях!

Ответ 10

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


Если первая (переопределяющая абстрактное свойство)

Этот код бесполезен. Только базовый класс не должен сказать вам, что вы вынуждены переопределить свойство 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; }
}

Если последний (переопределяет свойство get-only класса)

Каждое не абстрактное свойство имеет сеттер. В противном случае это бесполезно, и вы не должны его использовать. 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, потому что вы не хотите, чтобы базовый класс обрабатывал его, не имел доступа к просмотру, как он обрабатывает его, или ему было поручено hax some code real quick'n'dirty против вашей воли.

В Latter и Former (вывод)

Long-Story Short: Microsoft не обязана ничего менять. Вы можете выбрать, как настроен ваш класс реализации, и без конструктора использовать весь или ни один из базового класса.

Ответ 11

Решение только для небольшого подмножества прецедентов, но тем не менее: в С# 6.0 "readonly" setter автоматически добавляется для переопределенных свойств только для getter.

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

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

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

Ответ 12

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

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

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

Ответ 13

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

Ответ 14

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

Ответ 15

Это не невозможно. Вам просто нужно использовать "новое" ключевое слово в своей собственности. Например,

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

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

Надеюсь, что это поможет

Ответ 16

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

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

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

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

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