Просто чтобы убедиться, что мы находимся на той же странице, метод «скрывается» - это когда производный класс определяет элемент с тем же именем, что и в базовом классе (который, если это метод / свойство, не помечается как виртуальный / переопределяемый ), и когда вызывается из экземпляра производного класса в «производном контексте», используется производный член, в то время как при вызове тем же экземпляром в контексте его базового класса используется член базового класса. Это отличается от абстракции / переопределения членов, когда член базового класса ожидает, что производный класс определит замену, и модификаторов области видимости, которые «скрывают» элемент от потребителей за пределами желаемой области.
Короткий ответ на вопрос, почему это разрешено, заключается в том, что если этого не сделать, разработчики нарушат несколько ключевых принципов объектно-ориентированного проектирования.
Вот более длинный ответ; во-первых, рассмотрим следующую структуру классов в альтернативном юниверсе, где C # не позволяет скрывать элементы:
public interface IFoo
{
string MyFooString {get;}
int FooMethod();
}
public class Foo:IFoo
{
public string MyFooString {get{return "Foo";}}
public int FooMethod() {//incredibly useful code here};
}
public class Bar:Foo
{
//public new string MyFooString {get{return "Bar";}}
}
Мы хотим раскомментировать участника в Bar и тем самым разрешить Bar предоставить другую MyFooString. Однако мы не можем этого сделать, потому что это нарушило бы запрет альтернативной реальности на скрытие членов. Этот конкретный пример будет распространен на ошибки и является ярким примером того, почему вы можете захотеть его запретить; например, какую консольную информацию вы бы получили, если бы сделали следующее?
Bar myBar = new Bar();
Foo myFoo = myBar;
IFoo myIFoo = myFoo;
Console.WriteLine(myFoo.MyFooString);
Console.WriteLine(myBar.MyFooString);
Console.WriteLine(myIFoo.MyFooString);
Вдобавок ко всему, я на самом деле не уверен, что в последней строке вы увидите «Foo» или «Bar». Вы определенно получите «Foo» для первой строки и «Bar» для второй, хотя все три переменные ссылаются на один и тот же экземпляр с абсолютно одинаковым состоянием.
Таким образом, разработчики языка в нашей альтернативной вселенной препятствуют этому явно плохому коду, предотвращая скрытие свойств. Теперь вам, как программисту, действительно необходимо сделать именно это. Как вы обходите ограничения? Ну, один из способов - назвать собственность Бара по-другому:
public class Bar:Foo
{
public string MyBarString {get{return "Bar";}}
}
Совершенно законно, но это не то поведение, которое мы хотим. Экземпляр Bar всегда будет выдавать «Foo» для свойства MyFooString, когда мы хотели, чтобы он производил «Bar». Мы должны не только знать, что наш IFoo - это конкретно Bar, мы также должны знать, как использовать другой метод доступа.
Мы также могли бы, совершенно правдоподобно, забыть отношения родитель-потомок и напрямую реализовать интерфейс:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
public int FooMethod() {...}
}
Для этого простого примера это идеальный ответ, если вам важно, чтобы Foo и Bar были IFoo. Код использования, приведенный в нескольких примерах, не скомпилируется, потому что Bar не является Foo и не может быть назначен как таковой. Однако, если у Foo есть какой-то полезный метод "FooMethod", который нужен Bar, теперь вы не можете наследовать этот метод; вам придется либо клонировать его код в Bar, либо проявить творческий подход:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
private readonly theFoo = new Foo();
public int FooMethod(){return theFoo.FooMethod();}
}
Это очевидный взлом, и хотя некоторые реализации языковых спецификаций ОО составляют чуть больше этого, концептуально это неправильно; если потребители Bar должны раскрыть функциональность Foo, Bar должен быть Foo, а не Foo.
Очевидно, что если мы контролируем Foo, мы можем сделать его виртуальным, а затем переопределить его. Это концептуальная лучшая практика в нашем текущем юниверсе, когда ожидается, что член будет переопределен и будет применяться в любом альтернативном юниверсе, который не позволяет скрывать:
public class Foo:IFoo
{
public virtual string MyFooString {get{return "Foo";}}
//...
}
public class Bar:Foo
{
public override string MyFooString {get{return "Bar";}}
}
Проблема заключается в том, что доступ к виртуальному члену изнутри является относительно более дорогостоящим, и поэтому обычно вы хотите делать это только тогда, когда это необходимо. Однако отсутствие скрытия заставляет вас пессимистично относиться к членам, которые другой кодер, который не контролирует ваш исходный код, может захотеть переопределить; «Лучшая практика» для любого незапечатанного класса - делать все виртуальным, если только вы сами этого не хотели. Это также все еще не дает вам точное поведение сокрытия; строка всегда будет "Bar", если экземпляр является Bar. Иногда действительно полезно использовать слои скрытых данных о состоянии, основываясь на уровне наследования, на котором вы работаете.
Таким образом, разрешение скрывать членов является меньшим из этих зол. Отсутствие этого, как правило, ведет к худшим злодеяниям, совершаемым против объектно-ориентированных принципов, чем допускает это.