Почему следующий код 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
c#
.net
properties
getter-setter
ripper234
источник
источник
Foo
которое ничего не делает, кроме как обертывать защищенный абстрактныйGetFoo
метод. Абстрактный производный тип может затенять свойство с помощью невиртуального свойства чтения-записи, которое ничего не делает, кроме как обертывать вышеупомянутыйGetFoo
метод вместе с абстрактнымSetFoo
методом; конкретный производный тип мог бы делать то же самое, но предоставлять определения дляGetFoo
иSetFoo
.Ответы:
Потому что автор 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 - свойство только для чтения». Конечно, технически вы не нарушаете контракт ... вы делаете больше. Так что в каком-то смысле вы правы ... В заключение я бы сказал: «Сделайте так, чтобы недопонимание возникло как можно сильнее».
источник
InvalidOperationException("This instance is read-only")
.Я думаю, что основная причина просто в том, что синтаксис слишком явный, чтобы это могло работать по-другому. Этот код:
public override int MyProperty { get { ... } set { ... } }
весьма ясно , что как
get
иset
в переопределение. Вset
базовом классе нет, поэтому компилятор жалуется. Точно так же, как вы не можете переопределить метод, не определенный в базовом классе, вы также не можете переопределить установщик.Вы можете сказать, что компилятор должен угадывать ваше намерение и применять переопределение только к методу, который можно переопределить (то есть к получателю в данном случае), но это противоречит одному из принципов проектирования C # - компилятор не должен угадывать ваши намерения. , потому что он может ошибаться без вашего ведома.
Я думаю, что следующий синтаксис может подойти, но, как постоянно повторяет Эрик Липперт, реализация даже такой незначительной функции, как эта, по-прежнему требует больших усилий ...
public int MyProperty { override get { ... } set { ... } }
или, для автореализуемых свойств,
public int MyProperty { override get; set; }
источник
The modifier 'override' is not valid for this item
.Сегодня я наткнулся на ту же проблему, и я думаю, что у меня есть очень веская причина этого.
Во-первых, я хотел бы утверждать, что наличие свойства только для получения не обязательно означает доступ только для чтения. Я интерпретирую это как «Из этого интерфейса / абстракта вы можете получить это значение», это не означает, что некоторая реализация этого интерфейса / абстрактного класса не требует, чтобы пользователь / программа явно устанавливала это значение. Абстрактные классы служат для реализации части необходимой функциональности. Я не вижу абсолютно никаких причин, по которым унаследованный класс не может добавить сеттер без нарушения каких-либо контрактов.
Ниже приводится упрощенный пример того, что мне нужно сегодня. В итоге мне пришлось добавить сеттер в свой интерфейс, чтобы обойти это. Причина добавления установщика, а не добавления, скажем, метода 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 - Вы можете переопределить метод только для получения с помощью установщика, если хотите. В основном это просто:
Создайте
new
свойство с одинаковыми именами aget
и aset
.Если вы больше ничего не сделаете, старый
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; } } }
Проблема: Не удается -только с /
override
get
get
set
Вы хотите
override
использовать свойствоget
/set
, но оно не компилируется.public class C : B { private int _x; public override int X { get { return _x; } set { _x = value; } // Won't compile } }
Решение: используйте
abstract
промежуточный слой.Хотя вы не можете напрямую
override
использовать свойствоget
/set
, вы можете :Создайте
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
свойствам. Вы также можете использовать его для таких вещей, как:Изменение какого - либо имущества в
get
-Только,set
-только илиget
-и-set
собственности, независимо от того, каким он был в базовом классе.Измените тип возвращаемого значения метода в производных классах.
Основные недостатки заключаются в том, что необходимо выполнить больше кода и добавить лишнее
abstract class
в дереве наследования. Это может немного раздражать конструкторов, которые принимают параметры, потому что их нужно копировать / вставлять на промежуточный уровень.источник
Я согласен с тем, что невозможность переопределить метод получения в производном типе является анти-шаблоном. Только для чтения указывает на отсутствие реализации, а не на контракт чистого функционала (подразумеваемый ответом с наибольшим голосованием).
Я подозреваю, что у 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; }
источник
Возможно, вы могли бы обойти проблему, создав новое свойство:
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 решила, что должно быть три различных типа свойств: только для чтения, только для записи и для чтения-записи, только одно из которых может существовать с данной подписью в данном контексте; свойства могут быть переопределены только идентично объявленными свойствами. Чтобы сделать то, что вы хотите, необходимо создать два свойства с одинаковым именем и сигнатурой, одно из которых предназначено только для чтения, а другое - для чтения и записи.
Лично мне хотелось бы, чтобы вся концепция «свойств» была упразднена, за исключением того, что синтаксис свойств можно использовать как синтаксический сахар для вызова методов «get» и «set». Это не только упростит использование опции «добавить набор», но также позволит «get» вернуть тип, отличный от «set». Хотя такая возможность не будет использоваться очень часто, иногда может быть полезно, чтобы метод get возвращал объект-оболочку, в то время как «set» мог принимать либо оболочку, либо фактические данные.
источник
Вот обходной путь, чтобы добиться этого с помощью 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); }
Таким образом, мы можем установить значение объекта для свойства, доступного только для чтения. Однако может не работать во всех сценариях!
источник
Вы должны изменить заголовок вопроса, указав, что ваш вопрос касается только переопределения абстрактного свойства, или что ваш вопрос касается общего переопределения свойства класса только для получения.
Если первое (переопределение абстрактного свойства)
Этот код бесполезен. Сам по себе базовый класс не должен сообщать вам, что вы вынуждены переопределить свойство 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 не нужно ничего менять. Вы можете выбрать способ настройки вашего реализующего класса и, без конструктора, использовать весь базовый класс или ни один из них.
источник
Решение только для небольшого подмножества вариантов использования, но тем не менее: в 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; } }
источник
Потому что это нарушит концепцию инкапсуляции и сокрытия реализации. Рассмотрим случай, когда вы создаете класс, отправляете его, а затем потребитель вашего класса дает себе возможность установить свойство, для которого вы изначально предоставляете только геттер. Это эффективно нарушит любые инварианты вашего класса, от которых вы можете зависеть в своей реализации.
источник
Это не невозможно. Вам просто нужно использовать ключевое слово «новое» в своей собственности. Например,
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
больше не отменяет виртуальное базовое. В частности, если базовое свойство абстрактное , как в исходном посте, невозможно использоватьnew
ключевое слово в не абстрактном подклассе, потому что вам нужно переопределить все абстрактные члены.Test t = new Test(); Base b = t; Console.WriteLine(t.BaseProperty)
дает вам 5,Console.WriteLine(b.BaseProperty)
дает вам 0, и когда ваш преемник должен отладить это, вам лучше уйти далеко-далеко.Свойство только для чтения в базовом классе указывает, что это свойство представляет значение, которое всегда может быть определено внутри класса (например, значение перечисления, соответствующее (db-) контексту объекта). Таким образом, ответственность за определение ценности остается в пределах класса.
Добавление установщика вызовет здесь неудобную проблему: ошибка проверки должна произойти, если вы установите значение, отличное от единственного возможного значения, которое оно уже имеет.
Однако из правил часто бывают исключения. Вполне возможно, что, например, в одном производном классе контекст сужает возможные значения перечисления до 3 из 10, но пользователю этого объекта все равно нужно решить, какое из них правильное. Производный класс должен делегировать ответственность за определение значения пользователю этого объекта. Важно понимать , что пользователь этого объекта должен хорошо знать об этом исключении и брать на себя ответственность за установку правильного значения.
Моим решением в таких ситуациях было бы оставить свойство только для чтения и добавить новое свойство чтения и записи в производный класс для поддержки исключения. Переопределение исходного свойства просто вернет значение нового свойства. Новое свойство может иметь собственное имя, правильно указывающее контекст этого исключения.
Это также подтверждает правильное замечание Гишу: «Сделайте так, чтобы недопонимание было как можно сложнее».
источник
ReadableMatrix
классе (с двухаргументной только для чтения индексируется собственности) , имеющие подтипыImmutableMatrix
иMutableMatrix
. Код, который должен только читать матрицу и не заботится о том, может ли он измениться когда-нибудь в будущем, может принимать параметр типаReadableMatrix
и быть полностью доволен изменяемыми или неизменяемыми подтипами.List<T>.Count
делает: он не может быть назначен, но это не означает, что он доступен только для чтения в том смысле, что он не может быть изменен пользователями класса. Его вполне можно изменить, хотя и косвенно, путем добавления и удаления элементов списка.Потому что на уровне IL свойство чтения / записи преобразуется в два метода (получатель и установщик).
При переопределении вы должны продолжать поддерживать базовый интерфейс. Если бы вы могли добавить сеттер, вы бы эффективно добавляли новый метод, который оставался бы невидимым для внешнего мира, что касается интерфейса ваших классов.
Конечно, добавление нового метода не нарушит совместимость как таковую, но, поскольку он останется скрытым, решение запретить это имеет смысл.
источник
new
вместоoverride
.new
свои дополнения.Потому что у класса, у которого есть свойство только для чтения (без сеттера), вероятно, есть веская причина для этого. Например, может не быть какого-либо базового хранилища данных. Разрешение вам создать сеттер нарушает договор, установленный классом. Это просто плохое ООП.
источник