Как скрыть унаследованное свойство в классе без изменения унаследованного класса (базового класса)?

Если у меня есть следующий пример кода:

public class ClassBase
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class ClassA : ClassBase
{
    public int JustNumber { get; set; }

    public ClassA()
    {
        this.ID = 0;
        this.Name = string.Empty;
        this.JustNumber = string.Empty;
    }
}

Что мне делать, чтобы скрыть свойство Name (не показывать как члена членов ClassA) без изменения ClassBase?


person Ahmed Magdy    schedule 09.12.2009    source источник
comment
Это нарушение одного из основных принципов ООП (полиморфизм и, соответственно, L в SOLID).   -  person jason    schedule 09.12.2009
comment
В частности, нарушение принципа замены лисков (en.wikipedia.org/wiki/Liskov_substitution_principle)   -  person Robert Venables    schedule 09.12.2009
comment
Многие ответы игнорируют тот простой факт, что базовый класс может быть предоставлен третьей стороной. Поскольку этот код не является собственностью, его нельзя изменить. Желание создать более узкую версию базового класса, скрывая члены, имеет определенные основания. И лучше наследовать, чем использовать базовый элемент управления в качестве компонента нового элемента управления, поскольку это требует много дополнительной работы по раскрытию методов, уже найденных в базе.   -  person Mario    schedule 12.08.2014
comment
@Mario, если это так, я настоятельно рекомендую создать собственную реализацию класса и использовать объект для сопоставления объектов, чтобы установить нужные свойства. Таким образом, вы создаете свою более узкую версию стороннего класса.   -  person digaomatias    schedule 07.07.2015


Ответы (10)


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

Однако не отклоняйтесь слишком далеко от прямого вопроса. Если вы действительно хотели нарушить «правила» и хотите продолжить путь, который вы выбрали, вот как вы можете это сделать:

Соглашение состоит в том, чтобы реализовать свойство, но генерировать NotImplementedException при вызове этого свойства, хотя мне это тоже не нравится. Но это мое личное мнение, и оно не меняет того факта, что эта конвенция остается в силе.

Если вы пытаетесь сделать свойство устаревшим (и оно объявлено в базовом классе как виртуальное), вы можете либо использовать для него атрибут Obsolete:

[Obsolete("This property has been deprecated and should no longer be used.", true)]
public override string Name 
{ 
    get 
    { 
        return base.Name; 
    }
    set
    {
        base.Name = value;
    }
}

(Редактировать: Как указал Брайан в комментариях, второй параметр атрибута вызовет ошибку компиляции, если кто-то ссылается на свойство Name, поэтому он не сможет его использовать, даже если вы мы реализовали его в производном классе.)

Или, как я уже упоминал, используйте NotImplementedException:

public override string Name
{
    get
    {
        throw new NotImplementedException();
    }
    set
    {
        throw new NotImplementedException();
    }
}

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

public new string Name
{
    get
    {
        throw new NotImplementedException();
    }
    set
    {
        throw new NotImplementedException();
    }
}

Вы по-прежнему можете использовать атрибут Obsolete так же, как если бы метод был переопределен, или вы можете создать исключение NotImplementedException, в зависимости от того, что вы выберете. Я бы, вероятно, использовал:

[Obsolete("Don't use this", true)]
public override string Name { get; set; }

or:

[Obsolete("Don't use this", true)]
public new string Name { get; set; }

В зависимости от того, был ли он объявлен виртуальным в базовом классе.

person BenAlabaster    schedule 09.12.2009
comment
Устаревший атрибут также имеет второй параметр, указывающий, что использование свойства следует считать ошибкой. В этот момент вы должны получить ошибку времени компиляции, что полезно. - person Brian Hasden; 09.12.2009
comment
Спасибо, Брайан, я должен был упомянуть об этом, хороший улов. - person BenAlabaster; 09.12.2009
comment
Я считаю, что вы также можете использовать новое ключевое слово, чтобы указать новые функции для свойства, даже если оно не помечено как виртуальное. Это позволило бы ему пометить свойство как устаревшее, даже если оно относится к классу, в котором свойство не было виртуальным. - person Brian Hasden; 09.12.2009
comment
Да, вы, должно быть, добавили это, когда я публиковал свой комментарий. Ну, просто пытаюсь помочь. - person Brian Hasden; 09.12.2009
comment
Следует также отметить, что хотя использование new скроет исходную реализацию свойства для производного класса, исходная реализация все равно будет использоваться при приведении объекта к базовому классу, что может привести к неприятным ошибкам. - person Grizzly; 09.12.2009
comment
Очень верно. Очевидно, что пользователь, разместивший вопрос, пытается сделать это неправильно. Я не думаю, что есть надежный способ добиться того, что он пытается сделать. - person Brian Hasden; 09.12.2009
comment
@Grizzly - Мы указали, что это была плохая идея, прежде чем рассказали ОП, как этого добиться. Это один из побочных эффектов. - person BenAlabaster; 09.12.2009
comment
Публичные методы базового класса должны быть реализованы в производных классах в соответствии с LSP; защищенные свойства не имеют такого требования (поскольку единственный способ получить доступ к защищенным членам — из экземпляра базового объекта, который всегда относится к базовому типу). Каков предпочтительный способ скрыть защищенные члены от производных классов (например, чтобы запретить производному классу вызывать MemberwiseClone)? - person supercat; 17.07.2011
comment
Что бы я дал Microsoft, чтобы предоставить возможность (с помощью нового ключевого слова, атрибута или других средств) эффективно скрывать нежелательные члены в производных классах. Тогда я мог бы просто использовать класс UserControl вместо того, чтобы выполнять столько работы, когда я наследую класс Control. - person Alex Essilfie; 26.11.2014

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

[Browsable(false)]
[Bindable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Never)]

Это то, что System.Windows.Forms делает для элементов управления, свойства которых не подходят. Свойство Text, например, есть в Control, но оно не имеет смысла для каждого класса, наследуемого от Control. Итак, в MonthCalendar например, свойство Text выглядит так (согласно справочному онлайн-источнику):

[Browsable(false),
    EditorBrowsable(EditorBrowsableState.Never),
    Bindable(false), 
    DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override string Text {
    get { return base.Text; }
    set { base.Text = value; }
}
  • Возможность просмотра – отображается ли участник в окне свойств.
  • EditorBrowsable – отображается ли участник в раскрывающемся списке Intellisense.

EditorBrowsable(false) не помешает вам ввести свойство, и если вы используете это свойство, ваш проект все равно будет компилироваться. Но поскольку это свойство не отображается в Intellisense, не будет так очевидно, что вы можете его использовать.

person Ryan Lundy    schedule 09.12.2009

Просто спрячь это

 public class ClassBase
{
    public int ID { get; set; }
    public string Name { get; set; }
}
public class ClassA : ClassBase
{
    public int JustNumber { get; set; }
    private new string Name { get { return base.Name; } set { base.Name = value; } }
    public ClassA()
    {
        this.ID = 0;
        this.Name = string.Empty;
        this.JustNumber = 0;
    }
}

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

person CCondron    schedule 03.02.2011
comment
С помощью этого метода выполнение чего-то вроде ClassA.Name вне ClassA приведет к раскрытию свойства ClassBase.Name. Невозможно new предоставить члену более ограниченный доступ, чем это было объявлено изначально. - person Alex Essilfie; 26.11.2014
comment
В этом суть заметки. Имя не будет публичным членом ClassA в соответствии с вопросом. Для сериализации и отражения это может быть важно. - person CCondron; 02.12.2014
comment
Да, это правда. Однако стоит отметить, что когда кто-то хочет скрыть член от унаследованного класса, это может быть связано с тем, что он устанавливает значение члена в одно из набора предопределенных значений, и поэтому его изменение может привести к неожиданному поведению. - person Alex Essilfie; 02.12.2014
comment
Согласен, и, учитывая ограничение не изменять базовый класс, это невозможно остановить. - person CCondron; 03.12.2014
comment
Согласовано. В конце концов, при нажатии можно использовать отражение для изменения даже закрытых членов. - person Alex Essilfie; 03.12.2014
comment
Вместо всей риторики о том, почему этого не следует делать... ЭТО реальный ответ, который я искал. Для целей сериализации мне это действительно нужно. Это обеспечивает решение, в котором я нуждался идеально! Спасибо большое! - person SkyFighter; 02.11.2017

Зачем навязывать наследование, если в этом нет необходимости? Я думаю, что правильный способ сделать это — использовать has-a вместо is-a.

public class ClassBase
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class ClassA
{
    private ClassBase _base;

    public int ID { get { return this._base.ID; } }

    public string JustNumber { get; set; }

    public ClassA()
    {
        this._base = new ClassBase();
        this._base.ID = 0;
        this._base.Name = string.Empty;
        this.JustNumber = string.Empty;
    }
}
person flayn    schedule 27.06.2012

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

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

Лучший способ добиться этого сейчас — многоуровневое наследование.

public class classA 
{
}

public class B : A 
{} 

public class C : B 
{} 

Класс B выполняет всю вашу работу, а класс C предоставляет то, что вам нужно.

person engnrman    schedule 04.10.2012

Я полностью согласен с тем, что свойства не следует удалять из базовых классов, но иногда производный класс может иметь другой, более подходящий способ ввода значений. В моем случае, например, я наследую от ItemsControl. Как мы все знаем, ItemsControl имеет свойство ItemsSource, но я хочу, чтобы мой элемент управления объединял данные из 2-х источников (например, Person и Location). Если бы мне нужно было, чтобы пользователь вводил данные с помощью ItemsSource, мне нужно было бы разделить, а затем снова объединить значения, поэтому я создал 2 свойства для ввода данных. Но вернемся к исходному вопросу: это оставляет ItemsSource, который я не хочу, чтобы пользователь использовал, потому что я «заменяю» его своими собственными свойствами. Мне нравятся идеи Browsable и EditorBrowsable, но это все равно не мешает пользователю их использовать. Основной момент здесь заключается в том, что наследование должно сохранять БОЛЬШИНСТВО свойств, но когда имеется большой сложный класс (особенно такой, в котором вы не можете изменить исходный код), переписывать все было бы очень неэффективно.

person Nathan Sokalski    schedule 18.11.2016

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

Вы можете изменить реализацию, чтобы генерировать исключение при вызове свойства (если бы оно было виртуальным)...

person Fried Hoeben    schedule 09.12.2009

Вы можете использовать Browsable(false)

[Browsable( false )]
public override string Name
{
    get { return base.Name; }
    set { base.Name= value; }
}
person Abdus Salam Azad    schedule 23.10.2018

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

Почему?

Хороший дизайн состоит в том, чтобы позволить базовому классу иметь общие свойства, которыми обладает определенная концепция (виртуальная или реальная). Пример: System.IO.Stream в C#.

В дальнейшем плохой дизайн увеличит стоимость обслуживания и сделает внедрение все труднее и труднее. Избегайте этого, насколько это возможно!

Основные правила, которыми я пользуюсь:

  • Минимизируйте количество свойств и методов в базовом классе. Если вы не предполагаете использовать некоторые свойства или методы в классе, наследующем базовый класс; тогда не помещайте его в базовый класс. Если вы находитесь на стадии разработки проекта; всегда возвращайтесь к чертежной доске сейчас, а затем проверьте дизайн, потому что все меняется! Редизайн при необходимости. Когда ваш проект запущен, затраты на внесение изменений позже в дизайн возрастут!

    • Если вы используете базовый класс, реализованный третьей стороной, подумайте о том, чтобы «подняться» на один уровень вместо «переопределения» с помощью «NotImplementedException» или чего-то подобного. Если нет другого уровня, рассмотрите возможность разработки кода с нуля.

    • Всегда думайте о том, чтобы запечатать классы, которые вы не хотите, чтобы кто-либо мог наследовать. Это заставляет программистов «подниматься на один уровень вверх» в «иерархии наследования» и, таким образом, можно избежать «незавершенных концов», таких как «NotImplementedException».

person user3567581    schedule 24.04.2014

Я знаю, что вопрос старый, но вы можете переопределить PostFilterProperties следующим образом:

 protected override void PostFilterProperties(System.Collections.IDictionary properties)
    {
        properties.Remove("AccessibleDescription");
        properties.Remove("AccessibleName");
        properties.Remove("AccessibleRole");
        properties.Remove("BackgroundImage");
        properties.Remove("BackgroundImageLayout");
        properties.Remove("BorderStyle");
        properties.Remove("Cursor");
        properties.Remove("RightToLeft");
        properties.Remove("UseWaitCursor");
        properties.Remove("AllowDrop");
        properties.Remove("AutoValidate");
        properties.Remove("ContextMenuStrip");
        properties.Remove("Enabled");
        properties.Remove("ImeMode");
        //properties.Remove("TabIndex"); // Don't remove this one or the designer will break
        properties.Remove("TabStop");
        //properties.Remove("Visible");
        properties.Remove("ApplicationSettings");
        properties.Remove("DataBindings");
        properties.Remove("Tag");
        properties.Remove("GenerateMember");
        properties.Remove("Locked");
        //properties.Remove("Modifiers");
        properties.Remove("CausesValidation");
        properties.Remove("Anchor");
        properties.Remove("AutoSize");
        properties.Remove("AutoSizeMode");
        //properties.Remove("Location");
        properties.Remove("Dock");
        properties.Remove("Margin");
        properties.Remove("MaximumSize");
        properties.Remove("MinimumSize");
        properties.Remove("Padding");
        //properties.Remove("Size");
        properties.Remove("DockPadding");
        properties.Remove("AutoScrollMargin");
        properties.Remove("AutoScrollMinSize");
        properties.Remove("AutoScroll");
        properties.Remove("ForeColor");
        //properties.Remove("BackColor");
        properties.Remove("Text");
        //properties.Remove("Font");
    }
person Nane    schedule 14.06.2015
comment
Я думаю, что этот код специфичен для winforms или ASP, возможно, форм Xamarin. Упомяните это в своем ответе - person Kira; 23.02.2017