Solo para asegurarnos de que estamos en la misma página, el método "ocultar" es cuando una clase derivada define un miembro del mismo nombre como uno en la clase base (que, si es un método / propiedad, no se marca virtual / reemplazable ), y cuando se invoca desde una instancia de la clase derivada en "contexto derivado", se usa el miembro derivado, mientras que si es invocado por la misma instancia en el contexto de su clase base, se usa el miembro de la clase base. Esto es diferente de la abstracción / anulación de miembros donde el miembro de la clase base espera que la clase derivada defina un reemplazo, y de los modificadores de alcance / visibilidad que "ocultan" un miembro de los consumidores fuera del alcance deseado.
La respuesta breve a por qué está permitido es que no hacerlo obligaría a los desarrolladores a violar varios principios clave del diseño orientado a objetos.
Aquí está la respuesta más larga; primero, considere la siguiente estructura de clase en un universo alternativo donde C # no permite ocultar miembros:
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";}}
}
Queremos descomentar al miembro en Bar y, al hacerlo, permitir que Bar proporcione un MyFooString diferente. Sin embargo, no podemos hacerlo porque violaría la prohibición de la realidad alternativa de ocultar miembros. Este ejemplo particular sería abundante para los errores y es un excelente ejemplo de por qué es posible que desee prohibirlo; por ejemplo, ¿qué salida de consola obtendrías si hicieras lo siguiente?
Bar myBar = new Bar();
Foo myFoo = myBar;
IFoo myIFoo = myFoo;
Console.WriteLine(myFoo.MyFooString);
Console.WriteLine(myBar.MyFooString);
Console.WriteLine(myIFoo.MyFooString);
Fuera de mi cabeza, en realidad no estoy seguro de si obtendrías "Foo" o "Bar" en esa última línea. Definitivamente obtendría "Foo" para la primera línea y "Bar" para la segunda, a pesar de que las tres variables hacen referencia exactamente a la misma instancia con exactamente el mismo estado.
Entonces, los diseñadores del lenguaje, en nuestro universo alternativo, desalientan este código obviamente malo al evitar la ocultación de propiedades. Ahora, usted como codificador tiene una verdadera necesidad de hacer exactamente esto. ¿Cómo esquivas la limitación? Bueno, una forma es nombrar la propiedad de Bar de manera diferente:
public class Bar:Foo
{
public string MyBarString {get{return "Bar";}}
}
Perfectamente legal, pero no es el comportamiento que queremos. Una instancia de Bar siempre producirá "Foo" para la propiedad MyFooString, cuando queramos que produzca "Bar". No solo tenemos que saber que nuestro IFoo es específicamente un Bar, también tenemos que saber usar los diferentes accesorios.
También podríamos, muy plausiblemente, olvidar la relación padre-hijo e implementar la interfaz directamente:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
public int FooMethod() {...}
}
Para este simple ejemplo, es una respuesta perfecta, siempre y cuando solo le importe que Foo y Bar sean ambos IFoos. El código de uso de un par de ejemplos no podría compilarse porque un Bar no es un Foo y no se puede asignar como tal. Sin embargo, si Foo tenía algún método útil "FooMethod" que Bar necesitaba, ahora no puede heredar ese método; tendrías que clonar su código en Bar o ser creativo:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
private readonly theFoo = new Foo();
public int FooMethod(){return theFoo.FooMethod();}
}
Este es un truco obvio, y aunque algunas implementaciones de las especificaciones del lenguaje OO ascienden a poco más que esto, conceptualmente está mal; si los consumidores de bar necesidad de exponer la funcionalidad de Foo, Bar debería ser un Foo, no tiene un Foo.
Obviamente, si controlamos Foo, podemos hacerlo virtual y luego anularlo. Esta es la mejor práctica conceptual en nuestro universo actual cuando se espera que un miembro sea anulado, y se mantendría en cualquier universo alternativo que no permitiera ocultarse:
public class Foo:IFoo
{
public virtual string MyFooString {get{return "Foo";}}
//...
}
public class Bar:Foo
{
public override string MyFooString {get{return "Bar";}}
}
El problema con esto es que el acceso virtual de los miembros es, bajo el capó, relativamente más costoso de realizar, por lo que normalmente solo desea hacerlo cuando lo necesita. La falta de ocultación, sin embargo, te obliga a ser pesimista sobre los miembros que otro codificador que no controla tu código fuente podría querer volver a implementar; la "mejor práctica" para cualquier clase no sellada sería hacer que todo sea virtual a menos que específicamente no lo desees. También todavía no le otorga el comportamiento exacto de su escondite; la cadena siempre será "Bar" si la instancia es una barra. A veces es realmente útil aprovechar las capas de datos de estado ocultos, según el nivel de herencia en el que está trabajando.
En resumen, permitir que los miembros se escondan es el menor de estos males. No tenerlo generalmente conduciría a peores atrocidades cometidas contra principios orientados a objetos que permitirlo.