Почему невозможно переопределить свойство только для получения и добавить установщик?
Почему следующий код 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' не имеет переопределенного метода доступа к множеству
16 ответов
Поскольку автор 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 нельзя изменить. Но если set был возможен при деривации, этот класс мог бы обслуживать устаревшие значения, если кто-то внешне изменил свойство Bar объекта _source. Смысл в том, чтобыбыть открытым, избегать подлых дел и удивлять людей
Обновление: Илья Рыженков спрашивает: "Почему тогда интерфейсы не играют по тем же правилам?" Хм.. это становится более грязным, когда я думаю об этом.
Интерфейс - это контракт, который говорит: "ожидайте, что реализация будет иметь свойство чтения с именем Bar". Лично у меня гораздо меньше шансов сделать это предположение только для чтения, если я увидел интерфейс. Когда я вижу свойство get-only в интерфейсе, я читаю его как "Любая реализация выставит этот атрибут Bar"... в базовом классе он щелкает как "Bar - свойство только для чтения". Конечно, технически вы не нарушаете контракт.. вы делаете больше. Таким образом, вы в некотором смысле правы. Я бы сказал: "Сделайте все возможное, чтобы возникли недоразумения".
Я думаю, что основная причина заключается в том, что синтаксис слишком четкий, чтобы это работало любым другим способом. Этот код:
public override int MyProperty { get { ... } set { ... } }
вполне очевидно, что оба get
и set
являются переопределениями. Здесь нет set
в базовом классе, поэтому компилятор жалуется. Так же, как вы не можете переопределить метод, который не определен в базовом классе, вы также не можете переопределить метод установки.
Вы могли бы сказать, что компилятор должен угадать ваше намерение и применить переопределение только к методу, который может быть переопределен (например, к геттеру в данном случае), но это противоречит одному из принципов разработки C# - что компилятор не должен угадывать ваши намерения потому что это может угадать без вашего ведома.
Я думаю, что следующий синтаксис мог бы работать хорошо, но, как продолжает Эрик Липперт, реализация даже незначительной функции, подобной этой, все еще требует значительных усилий...
public int MyProperty
{
override get { ... }
set { ... }
}
или, для автоматически реализованных свойств,
public int MyProperty { override get; set; }
Возможно
tl; dr- Вы можете переопределить метод только для получения с помощью установщика, если хотите. Это в основном просто:
Создать
new
свойство, которое имеет какget
иset
используя то же имя.Если ты больше ничего не делаешь, то старый
get
Метод по-прежнему будет вызываться, когда производный класс вызывается через его базовый тип. Чтобы это исправить, добавьтеabstract
промежуточный слой, который используетoverride
на старомget
способ заставить его вернуть новый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
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
только собственность; вместо этого вы заменяете его, используя 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
в дереве наследования. Это может немного раздражать конструкторов, которые принимают параметры, потому что они должны быть скопированы / вставлены на промежуточном уровне.
Сегодня я столкнулся с той же самой проблемой и думаю, что у меня есть веская причина для этого.
Во-первых, я бы хотел сказать, что свойство get-only не обязательно переводит только в read-only. Я интерпретирую это как "Из этого интерфейса / вы можете получить это значение", это не означает, что для некоторой реализации этого интерфейса / абстрактного класса не понадобится пользователь / программа, чтобы установить это значение явно. Абстрактные классы служат для реализации части необходимой функциональности. Я не вижу абсолютно никакой причины, по которой унаследованный класс не мог бы добавить установщик без нарушения каких-либо контрактов.
Ниже приведен упрощенный пример того, что мне было нужно сегодня. Мне пришлось добавить сеттер в мой интерфейс, чтобы обойти это. Причина добавления установщика и не добавления, скажем, метода 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 здесь не применимо, оно просто не работает, и даже если бы оно имело место, оно не дало бы желаемый результат, а именно виртуальный метод получения, как описано в интерфейсе.
Я согласен с тем, что невозможность переопределить метод получения в производном типе является анти-паттерном. Только для чтения указывает на отсутствие реализации, а не контракт чистого функционала (подразумевается ответом голосования сверху).
Я подозреваю, что у 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;
}
}
ИМО, C# не должен ограничивать такие сценарии. Разработчик обязан использовать его соответствующим образом.
Проблема заключается в том, что по какой-либо причине Microsoft решила, что должно быть три различных типа свойств: только для чтения, только для записи и для чтения и записи, только одно из которых может существовать с данной сигнатурой в данном контексте; свойства могут быть переопределены только идентично объявленными свойствами. Чтобы сделать то, что вы хотите, необходимо создать два свойства с одинаковыми именем и подписью - одно из которых предназначено только для чтения, а другое - для чтения-записи.
Лично я хотел бы, чтобы вся концепция "свойств" могла быть отменена, за исключением того, что синтаксис свойства-ish мог бы использоваться как синтаксический сахар для вызова методов "get" и "set". Это не только облегчит опцию "add set", но также позволит "get" возвращать другой тип из "set". Хотя такая возможность не использовалась бы ужасно часто, иногда было бы полезно иметь метод 'get', возвращающий объект-обертку, в то время как 'set' мог бы принимать либо обертку, либо фактические данные.
Решение только для небольшого подмножества вариантов использования, но тем не менее: в 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;
}
}
Вы должны изменить заголовок вашего вопроса, указав либо, что ваш вопрос касается исключительно переопределения абстрактного свойства, либо что ваш вопрос касается общего переопределения свойства класса только для получения.
Если первое (переопределяя абстрактное свойство)
Этот код бесполезен. Один только базовый класс не должен сообщать вам, что вы вынуждены переопределять свойство 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;}
, имеет некоторый вид выставленного сеттера для этого свойства. Метаданные будут выглядеть так:
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 не обязательно что-либо менять. Вы можете выбрать, как будет настроен ваш реализующий класс, и без конструктора использовать весь базовый класс или его нет.
Вот обходной путь для достижения этого с помощью 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);
}
Таким образом, мы можем установить значение объекта для свойства, которое доступно только для чтения. Может не работать во всех сценариях, хотя!
Потому что это нарушило бы концепцию инкапсуляции и сокрытия реализации. Рассмотрим случай, когда вы создаете класс, отправляете его, а затем потребитель вашего класса дает возможность установить свойство, для которого вы изначально предоставляете только геттер. Это эффективно разрушит любые инварианты вашего класса, от которых вы можете зависеть в своей реализации.
Это не невозможно. Вам просто нужно использовать ключевое слово "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" разрывает контракт между реализацией базового класса и реализацией подкласса. Это необходимо, когда класс может иметь несколько контрактов (через интерфейс или базовый класс).
Надеюсь это поможет
Потому что на уровне IL свойство чтения / записи преобразуется в два метода (getter и setter).
При переопределении вы должны поддерживать базовый интерфейс. Если бы вы могли добавить установщик, вы бы эффективно добавляли новый метод, который оставался бы невидимым для внешнего мира, если говорить об интерфейсе ваших классов.
Правда, добавление нового метода само по себе не нарушит совместимость, но, поскольку он останется скрытым, решение запретить это имеет смысл.
Свойство только для чтения в базовом классе указывает, что это свойство представляет значение, которое всегда можно определить из класса (например, значение перечисления, соответствующее контексту (db-) объекта). Таким образом, ответственность за определение стоимости остается в классе.
Добавление сеттера могло бы вызвать здесь неловкую проблему: ошибка проверки должна возникать, если вы устанавливаете значение на что-то еще, кроме единственного возможного значения, которое оно уже имеет.
Правила часто имеют исключения, хотя. Вполне возможно, что, например, в одном производном классе контекст сужает возможные значения перечисления до 3 из 10, но пользователю этого объекта все еще необходимо решить, какой из них является правильным. Производный класс должен делегировать ответственность за определение значения пользователю этого объекта.Важно понимать, что пользователь этого объекта должен быть хорошо осведомлен об этом исключении и взять на себя ответственность, чтобы установить правильное значение.
Моим решением в подобных ситуациях было бы оставить свойство доступным только для чтения и добавить новое свойство чтения-записи в производный класс для поддержки исключения. Переопределение исходного свойства просто вернет значение нового свойства. Новое свойство может иметь собственное имя, которое правильно указывает контекст этого исключения.
Это также подтверждает правильное замечание: "сделай все возможное, чтобы возникли недоразумения" Гишу.
Потому что класс, который имеет свойство только для чтения (без установщика), вероятно, имеет для этого вескую причину. Например, не может быть никакого основного хранилища данных. Разрешение на создание сеттера нарушает контракт, установленный классом. Это просто плохо ООП.