Juste pour nous assurer que nous sommes sur la même page, la méthode de "masquage" est lorsqu'une classe dérivée définit un membre du même nom que celui de la classe de base (qui, s'il s'agit d'une méthode / propriété, n'est pas marqué virtuel / remplaçable ), et lorsqu'il est appelé à partir d'une instance de la classe dérivée dans le "contexte dérivé", le membre dérivé est utilisé, tandis que s'il est appelé par la même instance dans le contexte de sa classe de base, le membre de la classe de base est utilisé. Ceci est différent de l'abstraction / remplacement de membre où le membre de la classe de base attend de la classe dérivée qu'elle définisse un remplacement, et des modificateurs de portée / visibilité qui "cachent" un membre aux consommateurs en dehors de la portée souhaitée.
La réponse courte à la raison pour laquelle cela est autorisé est que ne pas le faire forcerait les développeurs à violer plusieurs principes clés de la conception orientée objet.
Voici la réponse la plus longue; tout d'abord, considérez la structure de classe suivante dans un univers alternatif où C # n'autorise pas le masquage des membres:
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";}}
}
Nous voulons décommenter le membre de Bar et, ce faisant, permettre à Bar de fournir une MyFooString différente. Cependant, nous ne pouvons pas le faire car cela violerait l'interdiction de la réalité alternative de cacher des membres. Cet exemple particulier serait plein de bogues et est un excellent exemple de pourquoi vous pourriez vouloir l'interdire; par exemple, quelle sortie de console obtiendriez-vous si vous faisiez ce qui suit?
Bar myBar = new Bar();
Foo myFoo = myBar;
IFoo myIFoo = myFoo;
Console.WriteLine(myFoo.MyFooString);
Console.WriteLine(myBar.MyFooString);
Console.WriteLine(myIFoo.MyFooString);
Du haut de ma tête, je ne sais pas vraiment si vous obtiendrez "Foo" ou "Bar" sur cette dernière ligne. Vous obtiendrez certainement "Foo" pour la première ligne et "Bar" pour la seconde, même si les trois variables font référence exactement à la même instance avec exactement le même état.
Ainsi, les concepteurs du langage, dans notre univers alternatif, découragent ce code manifestement mauvais en empêchant le masquage des propriétés. Maintenant, en tant que codeur, vous avez vraiment besoin de faire exactement cela. Comment contournez-vous la limitation? Eh bien, une façon consiste à nommer la propriété de Bar différemment:
public class Bar:Foo
{
public string MyBarString {get{return "Bar";}}
}
Parfaitement légal, mais ce n'est pas le comportement que nous voulons. Une instance de Bar produira toujours "Foo" pour la propriété MyFooString, quand nous voulions qu'elle produise "Bar". Non seulement nous devons savoir que notre IFoo est spécifiquement un bar, nous devons également savoir utiliser les différents accesseurs.
Nous pourrions également, de manière tout à fait plausible, oublier la relation parent-enfant et implémenter directement l'interface:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
public int FooMethod() {...}
}
Pour cet exemple simple, c'est une réponse parfaite, tant que vous vous souciez seulement du fait que Foo et Bar sont tous deux IFoos. Le code d'utilisation de quelques exemples ne pourrait pas être compilé car un Bar n'est pas un Foo et ne peut pas être attribué en tant que tel. Cependant, si Foo avait une méthode utile "FooMethod" dont Bar avait besoin, vous ne pouvez plus hériter de cette méthode; vous devrez soit cloner son code dans Bar, soit faire preuve de créativité:
public class Bar:IFoo
{
public string MyFooString {get{return "Bar";}}
private readonly theFoo = new Foo();
public int FooMethod(){return theFoo.FooMethod();}
}
Il s'agit d'un hack évident, et bien que certaines implémentations des spécifications du langage OO ne représentent guère plus que cela, conceptuellement, c'est faux; si les consommateurs de besoin Bar d'exposer les fonctionnalités de Foo, Bar devrait être un Foo, pas avoir un Foo.
Évidemment, si nous contrôlions Foo, nous pouvons le rendre virtuel, puis le remplacer. Il s'agit de la meilleure pratique conceptuelle dans notre univers actuel lorsqu'un membre est censé être remplacé, et se maintiendrait dans tout autre univers qui ne permettait pas de se cacher:
public class Foo:IFoo
{
public virtual string MyFooString {get{return "Foo";}}
//...
}
public class Bar:Foo
{
public override string MyFooString {get{return "Bar";}}
}
Le problème avec cela est que l'accès aux membres virtuels est, sous le capot, relativement plus coûteux à effectuer, et donc vous ne voulez généralement le faire que lorsque vous en avez besoin. Le manque de masquage, cependant, vous oblige à être pessimiste quant aux membres qu'un autre codeur qui ne contrôle pas votre code source pourrait vouloir réimplémenter; la «meilleure pratique» pour toute classe non scellée serait de tout rendre virtuel, sauf si vous ne le vouliez pas spécifiquement. Il a également encore ne vous donne pas le comportement exact de sa cachette; la chaîne sera toujours "Bar" si l'instance est une barre. Parfois, il est vraiment utile de tirer parti des couches de données d'état cachées, en fonction du niveau d'héritage auquel vous travaillez.
En résumé, permettre aux membres de se cacher est le moindre de ces maux. Ne pas l'avoir entraînerait généralement de pires atrocités commises contre des principes orientés objet que de le permettre.