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


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

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

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

CS0546 ' ConcreteClass.Бар.set': невозможно переопределить, потому что ' BaseClass.Бар' не имеет переопределяемого набора доступа

16 126

16 ответов:

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

Я с Microsoft на этом.
Предположим, я новый программист, которому было сказано кодировать против вывода базового класса. я пишу что-то, что предполагает, что 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 не может быть установлен в соответствии с интерфейсом базового класса, BarProvider предполагает, что кэширование является безопасным делом - поскольку Bar не может быть изменен. Но если set был возможен в производном, этот класс может обслуживать устаревшие значения, если кто-то изменил свойство Bar объекта _source извне. Дело в том, что'будьте открыты, избегайте делать подлые вещи и удивлять людей'

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

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

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

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

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

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

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

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

public int MyProperty { override get; set; }

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

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

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

вполне возможно

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

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

  2. если вы не делаете ничего другого, то старый get метод будет вызываться, когда производного класса вызывается через его базовый тип. Чтобы исправить это, добавьте abstract промежуточный слой, который использует override на старом 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 the 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. создать newget/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 ' ing the 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-только setили getиset свойство, независимо от того, что это было в базовом классе.

  2. изменить тип возвращаемого значения метода в производном занятия.

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

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

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

в неправильное представление, указанное в ответе top vote, о том, что свойство только для чтения должно быть каким-то образом более "чистым", чем свойство чтения/записи, смешно. Просто посмотрите на многие общие свойства только для чтения в рамках; значение не является константой / чисто функциональным; например, DateTime.Теперь только для чтения, но ничего, кроме чисто функционального значения. Попытка "кэшировать" значение свойства только для чтения, предполагая, что оно вернет то же значение в следующий раз, является рискованной.

в любом случае, я использовал одна из следующих стратегий для преодоления этого ограничения; оба они менее совершенны, но позволят вам хромать за пределами этого языкового дефицита:

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

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

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

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

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

вы не можете сделать что-нибудь такое:

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

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

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

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

вот обходной путь для достижения этого с помощью отражения:

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

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

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

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

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

решение только для небольшого подмножества вариантов использования, но тем не менее: в 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;
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

надеюсь, что это помогает