2010-12-09 2 views
5

Я переопределяю свойство в моем производном классе, которое я хотел бы сделать его только для чтения. Компилятор C# не позволит мне изменять модификаторы доступа, поэтому он должен оставаться открытым.Сделать свойство readonly в производном классе

Каков наилучший способ для этого? Должен ли я просто выбросить InvalidOperationException в set { }?

+0

Нам нужно больше контекста, я думаю. – Noldorin

ответ

8

Наличие сеттера в InvalidOperationException в производном классе нарушает Liskov Subsitution Principle. По сути делает использование сеттера контекстуальным для типа базового класса, что существенно исключает значение полиморфизма.

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

Один из способов обойти это - немного разбить иерархию.

class C1 { 
    public virtual int ReadOnlyProperty { get; } 
} 
class C2 { 
    public sealed override int ReadOnlyProperty { 
    get { return Property; } 
    } 
    public int Property { 
    get { ... } 
    set { ... } 
    } 
} 

Ваш тип у вас возникли проблемы с C1 могли наследовать в этом сценарии, а остальные могли бы перейти на проистекают из C2

+0

Итак, если базовый класс имеет свойство IsReadOnly и выбрасывает его собственное исключение, то все в порядке? Вот как работает наследование 'NameObjectCollectionBase'> NameValueCollection'>' HttpValueCollection', которое используется '' Request'Param' '' '' '' '' '' 'страницы. Я понимаю аргументы принципа, но в случае «NameObjectCollectionBase» это противоречит вашему утверждению: «Если сеттер не подходит при любых обстоятельствах, то он не принадлежит к базовому классу». К сожалению, я использую a. NET класс, который не реализовал это (по уважительной причине). Я думаю, что это имеет смысл в моем случае. –

+1

@ Нельсон, имеющий базовый класс, имеет 'IsReadOnly' ... компромисс в лучшем случае. Лично я думаю, что это ужасный шаблон, и BCL будет намного лучше, если коллекции будут правильно разделены на изменяемые и только для чтения интерфейсы/типы. – JaredPar

+0

Итак, я уточню. Я получаю от «ListView» ASP.NET. Назовем его 'MyCustomList'. Я хочу все функции/шаблоны 'ListView', но пусть' MyCustomList' обрабатывает данные, назначая 'DataSource/Id' и предотвращая изменение DataSource/Id'. Я думаю, что бросание исключения - хороший компромисс или повторная реализация всей функции «ListView». –

3

можно скрыть оригинальную реализацию и вернуть базовую реализацию:

class Foo 
{ 
    private string _someString; 
    public virtual string SomeString 
    { 
     get { return _someString; } 
     set { _someString = value; } 
    } 
} 

class Bar : Foo 
{ 
    public new string SomeString 
    { 
     get { return base.SomeString; } 
     private set { base.SomeString = value; } 
    } 
} 

namespace ConsoleApplication3 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Foo f = new Foo(); 
      f.SomeString = "whatever"; // works 
      Bar b = new Bar(); 
      b.SomeString = "Whatever"; // error 
     } 
    } 
} 

Однако ,. как упоминал Джаред, это довольно странная ситуация. Почему бы вам не сделать свой сеттер закрытым или защищенным в базовом классе?

+0

За исключением того, что если вы делаете «Foo f2 = b; f.SomeString =« eeba geeba! »;» Из вашего примера, компилятор позволяет вам. – mjfgates

+0

@mjfgates: Я предполагаю, что вы имеете в виду 'Foo f = new Bar(); f.SomeString = "blah" '? Вот почему я не пошел по этому пути. Я не могу изменить базовый класс, так как я получаю от стандартного веб-управления ASP.NET. –

+0

Нет, я имел в виду то, что написал. В этом случае вы вызываете Foo.SomeString, потому что SomeString не является полиморфным в Bar, он скрывает реализацию Foo. –

0

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

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

0

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

0

Есть случаи, когда шаблон, который вы описываете, оправдан. Например, может быть полезно иметь абстрактный класс MaybeMutableFoo, из которого производные подтипы MutableFoo и ImmutableFoo. Все три класса включают имущество IsMutable и методы AsMutable(), AsNewMutable() и AsImmutable().

В этой ситуации было бы полностью правильным, чтобы MaybeMutableFoo обнаружил свойство чтения-записи, если его контракт явно указывает, что установщик может не работать, если IsMutable не возвращает true. Объект, который имеет поле типа MaybeMutableFoo, которое имеет место с экземпляром ImmutableFoo, может быть совершенно доволен этим экземпляром, если до или после него он не должен был писать на него, после чего он заменил бы поле объектом, возвращаемым через AsMutable(), а затем использовал его как измененный foo (он знал бы, что он изменен, поскольку он только что заменил его).При наличии MaybeMutableFoo включите сеттер, чтобы избежать необходимости делать какие-либо будущие придания типов в поле, как только оно было сделано для обращения к изменяемому экземпляру.

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

public class MaybeMutableFoo 
{ 
    public string Foo {get {return Foo_get();} set {Foo_set(value);} 
    protected abstract string Foo_get(); 
    protected abstract void Foo_set(string value}; 
} 

то производный класс ImmutableFoo может объявить:

new public string Foo {get {return Foo_get();} 

сделать его Foo свойство только для чтения, не мешая с возможностью кодировать переопределения для абстрактного Foo_get() и Foo_set() методов. Обратите внимание, что замена только для чтения Foo не изменяет поведение свойства get; цепочки версий, доступные только для чтения, для того же метода базового класса, что и свойство базового класса. Выполнение этого способа гарантирует, что есть одна точка патча для изменения свойства getter и одна для изменения сеттера, и эти точки патча не будут меняться, даже если само свойство будет переопределено.

1

Я думаю, что я такая ситуация лучшее решением является новым ключевым словом

public class Aclass {   
    public int ReadOnly { get; set; } 
} 

public class Bclass : Aclass {   
    public new int ReadOnly { get; private set; } 
} 
+0

С помощью нового ключевого слова вы можете скрыть базовые get и set accessors – Dimitrios

Смежные вопросы