Просто для того, щоб переконатися, що ми знаходимося на одній сторінці, метод "приховування" - це те, коли похідний клас визначає однойменний член, як один у базовому класі (який, якщо це метод / властивість, не позначається віртуальним / замінюваним ), і коли викликається екземпляр похідного класу у "похідному контексті", використовується похідний член, тоді як якщо він викликається тим самим екземпляром у контексті його базового класу, використовується базовий клас. Це відрізняється від абстрагування / переосмислення членів, коли член базового класу очікує, що похідний клас визначить заміну, і від модифікаторів обсягу / видимості, які "приховують" члена від споживачів за межами бажаної області.
Коротка відповідь на те, чому це дозволено, полягає в тому, що не робити цього змушує розробників порушувати кілька ключових положень об’єктно-орієнтованого дизайну.
Ось довша відповідь; спочатку розглянемо таку структуру класів у альтернативному всесвіті, де 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" для другого, навіть якщо всі три змінні посилаються на абсолютно той самий екземпляр з точно таким же станом.
Отже, дизайнери мови в нашому альтернативному Всесвіті відштовхують цей очевидно поганий код, запобігаючи приховуванню власності. Тепер ви як кодер маєте справжню потребу зробити саме це. Як ти обійдеш обмеження? Ну, один із способів - по-іншому назвати властивість Bar:
public class Bar:Foo
{
public string MyBarString {get{return "Bar";}}
}
Ідеально законно, але ми не хочемо поведінки. Екземпляр Bar завжди створюватиме "Foo" для властивості MyFooString, коли ми хотіли, щоб він створював "Bar". Мало того, що ми повинні знати, що наш IFoo - це спеціально бар, ми також мусимо знати інший аксесуар.
Ми також могли, цілком правдоподібно, забути про відносини батько-дитина та реалізувати інтерфейс безпосередньо:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
public int FooMethod() {...}
}
На цей простий приклад - ідеальна відповідь, якщо ви дбаєте лише про те, щоб Фу і Бар були обома IFoos. Код використання, який наведено в декількох прикладах, не вдасться скласти, оскільки бар не є Foo і не може бути призначений як такий. Однак якщо у Foo був якийсь корисний метод "FooMethod", який потрібен Бар, тепер ви не можете успадкувати цей метод; вам або доведеться клонувати його код у Bar, або бути творчим:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
private readonly theFoo = new Foo();
public int FooMethod(){return theFoo.FooMethod();}
}
Це очевидний злом, і хоча деякі реалізації специфікацій мови OO коштують трохи більше, ніж це, концептуально це неправильно; якщо споживачі Бар необхідності піддавати функціональності F, в барі повинен бути 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. Іноді справді корисно використовувати шари прихованих даних про стан, виходячи з рівня спадкування, на якому ви працюєте.
Підсумовуючи це, дозволяти членам ховатися є меншим з цих злом. Його відсутність, як правило, призведе до більш жорстоких злодійств, вчинених на об'єктно-орієнтованих принципах, ніж це дозволяють.