Apenas para garantir que estamos na mesma página, o método "oculto" é quando uma classe derivada define um membro com o mesmo nome que um membro da classe base (que, se for um método / propriedade, não é marcado como virtual / substituível ) e, quando invocado de uma instância da classe derivada no "contexto derivado", o membro derivado é usado, enquanto que, se invocado pela mesma instância no contexto de sua classe base, o membro da classe base é usado. Isso é diferente da abstração / substituição do membro, na qual o membro da classe base espera que a classe derivada defina uma substituição e dos modificadores de escopo / visibilidade que "ocultam" um membro dos consumidores fora do escopo desejado.
A resposta curta para por que é permitido é que isso não forçaria os desenvolvedores a violar vários princípios fundamentais do design orientado a objetos.
Aqui está a resposta mais longa; primeiro, considere a seguinte estrutura de classes em um universo alternativo em que o C # não permite ocultar membros:
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 o membro em Bar e, ao fazê-lo, permitir que Bar forneça um MyFooString diferente. No entanto, não podemos fazê-lo porque isso violaria a proibição de realidade alternativa ao esconderijo de membros. Este exemplo em particular estaria repleto de bugs e é um excelente exemplo de por que você pode querer bani-lo; por exemplo, qual saída do console você obteria se fizesse o seguinte?
Bar myBar = new Bar();
Foo myFoo = myBar;
IFoo myIFoo = myFoo;
Console.WriteLine(myFoo.MyFooString);
Console.WriteLine(myBar.MyFooString);
Console.WriteLine(myIFoo.MyFooString);
No topo da minha cabeça, na verdade não tenho certeza se você conseguiria "Foo" ou "Bar" nessa última linha. Você definitivamente obteria "Foo" para a primeira linha e "Bar" para a segunda, embora todas as três variáveis façam referência exatamente à mesma instância com exatamente o mesmo estado.
Assim, os designers da linguagem, em nosso universo alternativo, desencorajam esse código obviamente ruim, impedindo a ocultação de propriedades. Agora, você como codificador tem uma necessidade genuína de fazer exatamente isso. Como você contorna a limitação? Bem, uma maneira é nomear a propriedade de Bar de maneira diferente:
public class Bar:Foo
{
public string MyBarString {get{return "Bar";}}
}
Perfeitamente legal, mas não é o comportamento que queremos. Uma instância de Bar sempre produzirá "Foo" para a propriedade MyFooString, quando desejamos que ela produza "Bar". Não apenas precisamos saber que nosso IFoo é especificamente uma barra, também precisamos saber para usar o acessador diferente.
Também podemos, de maneira bastante plausível, esquecer o relacionamento pai-filho e implementar a interface diretamente:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
public int FooMethod() {...}
}
Para este exemplo simples, é uma resposta perfeita, desde que você se preocupe apenas com Foo e Bar serem ambos IFoos. O código de uso de alguns exemplos falharia em compilar porque uma barra não é um Foo e não pode ser atribuída como tal. No entanto, se o Foo tivesse algum método útil "FooMethod" que o Bar precisava, agora você não poderá herdar esse método; você precisaria clonar seu código em Bar ou ser criativo:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
private readonly theFoo = new Foo();
public int FooMethod(){return theFoo.FooMethod();}
}
Este é um truque óbvio e, embora algumas implementações das especificações da linguagem OO cheguem a pouco mais do que isso, conceitualmente está errado; se os consumidores de Bar necessidade de expor a funcionalidade do Foo, Bar deve ser um Foo, não têm uma Foo.
Obviamente, se controlamos o Foo, podemos torná-lo virtual e substituí-lo. Esta é a melhor prática conceitual em nosso universo atual, quando se espera que um membro seja substituído, e se manteria em qualquer universo alternativo que não permitisse ocultar:
public class Foo:IFoo
{
public virtual string MyFooString {get{return "Foo";}}
//...
}
public class Bar:Foo
{
public override string MyFooString {get{return "Bar";}}
}
O problema disso é que o acesso de membro virtual é, relativamente, mais caro de executar e, portanto, você normalmente deseja fazê-lo apenas quando necessário. A falta de ocultação, no entanto, força você a ser pessimista em relação aos membros que outro codificador que não controla seu código-fonte pode querer reimplementar; a "melhor prática" para qualquer classe não selada seria tornar tudo virtual, a menos que você não desejasse especificamente. Também ainda não fornece o comportamento exato de se esconder; a cadeia sempre será "Bar" se a instância for uma barra. Às vezes, é realmente útil aproveitar as camadas de dados de estado oculto, com base no nível de herança em que você está trabalhando.
Em resumo, permitir a ocultação de membros é o menor desses males. Não tê-lo geralmente levaria a piores atrocidades cometidas contra princípios orientados a objetos do que permitir.