it-swarm-ru.tech

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

Почему следующий код 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' не имеет переопределенного метода доступа к множеству

130
ripper234

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

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

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

1
Gishu

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

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

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

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

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

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

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

public int MyProperty { override get; set; }
31
Roman Starkov

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

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

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

19
JJJ

Возможно

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

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

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

Это позволяет нам переопределять свойства с getset, даже если им не хватало одного в их базовом определении.

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

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

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

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

    • вы можете использовать более производный тип возврата _/ifвы делаете его только 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; } }
}

Проблема: Нельзя overrideget- только с getset

Вы хотите использовать 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; }
}

Затем вы пишете класс, который не будет компилироваться раньше. На этот раз он будет скомпилирован, потому что вы на самом деле не overrideget- 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- only, set- only или get- и -set, независимо от того, какое оно было в базовом классе.

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

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

12
Nat

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

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

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

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

1
supercat

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

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

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

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

1
Thomas Danecker

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

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

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

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

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


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

Этот код бесполезен. Один только базовый класс не должен сообщать вам, что вы вынуждены переопределять свойство Get-Only (возможно, Interface). Базовый класс обеспечивает общую функциональность, которая может потребовать определенного ввода от реализующего класса. Следовательно, общая функциональность может вызывать абстрактные свойства или методы. В данном случае методы общей функциональности должны запрашивать переопределение абстрактного метода, такого как:

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;}, имеетSOMEсвоего рода открытый установщик для этого свойства. Метаданные будут выглядеть так:

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

И в последнем, и в бывшем (Заключение)

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

0
Suamere

Вот обходной путь для достижения этого с помощью 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);
}

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

0
user2514880