Per assicurarci di essere sulla stessa pagina, il metodo "nascondere" è quando una classe derivata definisce un membro con lo stesso nome di uno nella classe base (che, se si tratta di un metodo / proprietà, non è contrassegnato virtuale / overridable) e quando viene invocato da un'istanza della classe derivata in "contesto derivato", viene utilizzato il membro derivato, mentre se invocato dalla stessa istanza nel contesto della sua classe base, viene utilizzato il membro della classe base. Questo è diverso dall'estrazione / sovrascrizione dei membri in cui il membro della classe base si aspetta che la classe derivata definisca una sostituzione e dai modificatori di visibilità / visibilità che "nascondono" un membro dai consumatori al di fuori dell'ambito desiderato.
La breve risposta al motivo per cui è consentito è che non farlo forzerebbe gli sviluppatori a violare diversi principi chiave del design orientato agli oggetti.
Ecco la risposta più lunga; per prima cosa, considera la seguente struttura di classe in un universo alternativo in cui C # non consente l'occultamento dei membri:
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";}}
}
Vogliamo decommentare il membro in Bar e, così facendo, consentire a Bar di fornire un MyFooString diverso. Tuttavia, non possiamo farlo perché violerebbe la proibizione della realtà alternativa sul nascondersi dei membri. Questo particolare esempio sarebbe pieno di bug ed è un ottimo esempio del perché si potrebbe voler vietarlo; per esempio, quale output della console verrebbe ottenuto se hai fatto quanto segue?
Bar myBar = new Bar();
Foo myFoo = myBar;
IFoo myIFoo = myFoo;
Console.WriteLine(myFoo.MyFooString);
Console.WriteLine(myBar.MyFooString);
Console.WriteLine(myIFoo.MyFooString);
In cima alla mia testa, in realtà non sono sicuro se avresti ottenuto "Foo" o "Bar" su quest'ultima riga. Avrai sicuramente "Foo" per la prima riga e "Bar" per il secondo, anche se tutte e tre le variabili fanno riferimento esattamente alla stessa istanza con esattamente lo stesso stato.
Quindi, i progettisti del linguaggio, nel nostro universo alternativo, scoraggiano questo codice ovviamente nocivo impedendo il nascondimento delle proprietà. Ora, tu come programmatore hai una vera necessità di fare esattamente questo. Come si aggira la limitazione? Bene, un modo è di nominare la proprietà di Bar in modo diverso:
public class Bar:Foo
{
public string MyBarString {get{return "Bar";}}
}
Perfettamente legale, ma non è il comportamento che vogliamo. Un'istanza di Bar produrrà sempre "Foo" per la proprietà MyFooString, quando volevamo che producesse "Bar". Non solo dobbiamo sapere che il nostro IFoo è specificamente una barra, dobbiamo anche sapere di usare i diversi accessori.
Potremmo anche, abbastanza plausibilmente, dimenticare la relazione genitore-figlio e implementare direttamente l'interfaccia:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
public int FooMethod() {...}
}
Per questo semplice esempio è una risposta perfetta, purché ti interessi solo che Foo e Bar siano entrambi IFoos. Il codice di utilizzo di un paio di esempi non riuscirebbe a compilare perché una barra non è un Foo e non può essere assegnata come tale. Tuttavia, se Foo aveva un metodo utile "FooMethod" di cui aveva bisogno Bar, ora non puoi ereditare quel metodo; dovresti clonare il codice in Bar o diventare creativo:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
private readonly theFoo = new Foo();
public int FooMethod(){return theFoo.FooMethod();}
}
Questo è un trucco ovvio, e mentre alcune implementazioni delle specifiche del linguaggio O-O equivalgono a poco più di questo, concettualmente è sbagliato; se i consumatori di Bar hanno bisogno di esporre le funzionalità di Foo, Bar dovrebbe essere un Foo, non avere un Foo.
Ovviamente, se controlliamo Foo, possiamo renderlo virtuale, quindi sovrascriverlo. Questa è la migliore pratica concettuale nel nostro universo corrente quando ci si aspetta che un membro venga ignorato e che conservi in qualsiasi universo alternativo che non consentisse il nascondimento:
public class Foo:IFoo
{
public virtual string MyFooString {get{return "Foo";}}
//...
}
public class Bar:Foo
{
public override string MyFooString {get{return "Bar";}}
}
Il problema con questo è che l'accesso ai membri virtuali è, sotto il cofano, relativamente più costoso da eseguire, e quindi in genere lo si vuole fare solo quando è necessario. La mancanza di nascondigli, tuttavia, ti costringe a essere pessimista sui membri che un altro coder che non controlla il tuo codice sorgente potrebbe voler reimplementare; la "migliore pratica" per qualsiasi classe non sigillata sarebbe quella di rendere tutto virtuale, a meno che tu non lo volessi. Inoltre ancora non ti dà il comportamento esatto di nascondere; la stringa sarà sempre "Bar" se l'istanza è una barra. A volte è davvero utile sfruttare i livelli dei dati di stato nascosti, in base al livello di ereditarietà su cui stai lavorando.
In sintesi, consentire ai membri di nascondersi è il minore di questi mali. Il non averlo comporterebbe generalmente atrocità peggiori commesse contro i principi orientati agli oggetti piuttosto che permettere che lo faccia.