web-dev-qa-db-fra.com

C # - Les méthodes héritées du public peuvent-elles être masquées (par exemple, passer du privé à la classe dérivée)

Supposons que j'ai BaseClass avec les méthodes publiques A et B et que je crée DerivedClass par héritage.

par exemple.

public DerivedClass : BaseClass {}

Maintenant, je souhaite développer une méthode C dans DerivedClass qui utilise A et B. Existe-t-il un moyen de remplacer les méthodes A et B pour qu'elles soient privées dans DerivedClass afin que seule la méthode C soit exposée à quelqu'un qui souhaite utiliser DerivedClass?

54
Lyndon

Ce n'est pas possible, pourquoi?

En C #, il vous est imposé que si vous héritez des méthodes publiques, vous devez les rendre publiques. Sinon, ils s'attendent à ce que vous ne tiriez pas de la classe en premier lieu.

Au lieu d'utiliser la relation is-a, vous devez utiliser la relation has-a. 

Les concepteurs de langage n'autorisent pas cela à dessein, de sorte que vous utilisiez mieux l'héritage. 

Par exemple, on peut confondre accidentellement une classe Car dérivée d'un moteur de classe pour obtenir ses fonctionnalités. Mais un moteur est une fonctionnalité utilisée par la voiture. Donc, vous voudriez utiliser la relation has-a. L'utilisateur de la voiture ne veut pas avoir accès à l'interface du moteur. Et la voiture elle-même ne doit pas confondre les méthodes du moteur avec les siennes. Les dérivations futures de Nor Car. 

Donc, ils ne permettent pas de vous protéger contre les mauvaises hiérarchies d'héritage.

Que devriez-vous faire à la place?

Au lieu de cela, vous devez implémenter des interfaces. Cela vous laisse libre d'avoir des fonctionnalités utilisant la relation has-a.

Autres langues:

En C++, vous spécifiez simplement un modificateur avant la classe de base privée, publique ou protégée. Cela rend tous les membres de la base qui étaient publics à ce niveau d'accès spécifié. Il me semble stupide que vous ne puissiez pas faire la même chose en C #.

Le code restructuré:

interface I
{
    void C();
}

class BaseClass
{
    public void A() { MessageBox.Show("A"); }
    public void B() { MessageBox.Show("B"); }
}

class Derived : I
{
    public void C()
    {
        b.A();
        b.B();
    }

    private BaseClass b;
}

Je comprends que les noms des classes ci-dessus sont un peu fous :)

Autres suggestions:

D'autres ont suggéré de rendre publics les A() et B() et de lever des exceptions. Mais cela ne fait pas un cours convivial pour les gens et cela n’a pas vraiment de sens. 

68
Brian R. Bondy

Par exemple, lorsque vous essayez d’hériter d’un List<object> et que vous souhaitez masquer le membre direct Add(object _ob):

// the only way to hide
[Obsolete("This is not supported in this class.", true)]
public new void Add(object _ob)
{
    throw NotImplementedException("Don't use!!");
}

Ce n'est pas vraiment la solution la plus préférable, mais elle fait le travail. Intellisense accepte toujours, mais au moment de la compilation, vous obtenez une erreur:

erreur CS0619: 'TestConsole.TestClass.Add (TestConsole.TestObject)' est obsolète: 'Ceci n'est pas pris en charge dans cette classe.'

22
nono

Cela semble être une mauvaise idée. Liskov ne serait pas impressionné. 

Si vous ne voulez pas que les consommateurs de DerivedClass puissent accéder aux méthodes DeriveClass.A () et DerivedClass.B (), je suggérerais que DerivedClass implémente une interface publique IW WhateverMethodCIsAbout et que les consommateurs de DerivedClass discutent avec IWlanMethodCIsAbout and know rien du tout sur l'implémentation de BaseClass ou DerivedClass.

6
Hamish Smith

Ce dont vous avez besoin, c'est de la composition, pas de l'héritage.

class Plane
{
  public Fly() { .. }
  public string GetPilot() {...}
}

Maintenant, si vous avez besoin d’un type d’avion spécial, tel que celui qui a PairOfWings = 2 mais fait tout ce que peut faire un avion. Vous héritez de cet avion. Par ceci, vous déclarez que votre dérivation respecte le contrat de la classe de base et peut être remplacée sans clignoter partout où une classe de base est prévue. par exemple. LogFlight (Plan) continuerait à fonctionner avec une instance BiPlane.

Toutefois, si vous souhaitez simplement créer le comportement Fly pour un nouvel oiseau et que vous ne souhaitez pas prendre en charge le contrat de classe de base complet, vous composez à la place. Dans ce cas, refactorisez le comportement des méthodes à réutiliser dans un nouveau type Flight. Créez et conservez maintenant des références à cette classe dans Plane et Bird . Vous n'héritez pas car Bird ne prend pas en charge le contrat de classe de base complet ... (par exemple, il ne peut pas fournir GetPilot ()). 

Pour la même raison, vous ne pouvez pas réduire la visibilité des méthodes de classe de base lorsque vous écrasez .. vous pouvez écraser et rendre publique une méthode privée de base dans la dérivation, mais pas l'inverse. par exemple. Dans cet exemple, si je dérive un type d'avion "BadPlane", puis substitue et "masque" GetPilot () - le rend privé; une méthode client LogFlight (Plan p) fonctionnera pour la plupart des avions, mais explose pour "BadPlane" si la mise en oeuvre de LogFlight arrive à nécessiter/appelle GetPilot (). Étant donné que toutes les dérivations d'une classe de base sont censées être "substituables" chaque fois qu'un paramètre de classe de base est attendu, cela doit être interdit.

5
Gishu

À ma connaissance, la seule façon de procéder consiste à utiliser une relation Has-A et à implémenter uniquement les fonctions que vous souhaitez exposer.

3
Nescio

@Brian R. Bondy m'a signalé un article intéressant sur Cacher par héritage et le mot clé new.

http://msdn.Microsoft.com/en-us/library/aa691135(VS.71).aspx

Donc, comme solution de contournement, je suggérerais:

class BaseClass
{
    public void A()
    {
        Console.WriteLine("BaseClass.A");
    }

    public void B()
    {
        Console.WriteLine("BaseClass.B");
    }
}

class DerivedClass : BaseClass
{
    new public void A()
    {
        throw new NotSupportedException();
    }

    new public void B()
    {
        throw new NotSupportedException();
    }

    public void C()
    {
        base.A();
        base.B();
    }
}

De cette façon, un code comme celui-ci jettera une NotSupportedException:

    DerivedClass d = new DerivedClass();
    d.A();
3
Jorge Ferreira

Je dirais que si vous avez une base de code avec laquelle vous voulez faire cela, ce n'est pas la base de code la mieux conçue. C'est généralement le signe qu'une classe d'un niveau de la hiérarchie a besoin d'une certaine signature publique alors qu'une autre classe dérivée de cette classe n'en a pas besoin.

Un nouveau paradigme de codage est appelé "Composition sur l'héritage". Cela déroge directement aux principes du développement orienté objet (en particulier le principe de responsabilité unique et le principe d'ouverture/fermeture).

Malheureusement, comme beaucoup de développeurs en étaient formés à l'orientation objet, nous avons pris l'habitude de penser immédiatement à l'héritage plutôt qu'à la composition. Nous avons tendance à avoir de plus grandes classes qui ont de nombreuses responsabilités différentes simplement parce qu'elles peuvent être contenues dans le même objet "Real World". Cela peut conduire à des hiérarchies de classes de plus de 5 niveaux.

Un effet secondaire regrettable auquel les développeurs ne pensent généralement pas lorsqu'ils traitent en matière d'héritage est que celui-ci constitue l'une des formes de dépendance les plus puissantes que vous puissiez introduire dans votre code. Votre classe dérivée dépend désormais fortement de la classe dont elle a été héritée. Cela peut rendre votre code plus fragile à long terme et engendrer des problèmes complexes, car la modification d'un comportement donné dans une classe de base casse les classes dérivées de manière obscure.

Un moyen de dissocier votre code consiste à utiliser des interfaces telles que celles mentionnées dans une autre réponse. C'est une bonne chose à faire, car vous voulez que les dépendances externes d'une classe soient liées à des abstractions, pas à des types concrets/dérivés. Cela vous permet de changer d'implémentation sans changer d'interface, sans affecter une ligne de code dans votre classe dépendante.

Je préférerais beaucoup plus que de maintenir un système avec des centaines/milliers/encore plus de classes petites et faiblement couplées que de traiter avec un système qui utilise beaucoup le polymorphisme/héritage et qui comporte moins de classes plus étroitement couplées.

Peut-être que la ressource meilleure sur le développement orienté objet est le livre de Robert C. Martin, Développement logiciel agile, principes, modèles et pratiques .

1
Jason Olson

Se cacher est une pente assez glissante. Les principaux problèmes, IMO, sont les suivants:

  • Cela dépend du type de déclaration au moment du design de l'instance, ce qui signifie que si vous faites quelque chose comme BaseClass obj = new SubClass (), puis appelez obj.A (), le masquage est annulé. BaseClass.A () sera exécuté.

  • Le masquage peut très facilement masquer un comportement (ou des changements de comportement) dans le type de base. Ceci est évidemment moins préoccupant lorsque vous possédez les deux côtés de l'équation, ou si l'appel de 'base.xxx' fait partie de votre sous-membre.

  • Si vous possédez les deux côtés de l'équation base/sous-classe, vous devriez alors être en mesure de concevoir une solution plus gérable que le masquage/occultage institutionnalisé .
1
Jared

Alors que la réponse à la question est "non", je voudrais signaler un conseil aux autres personnes arrivant ici (étant donné que le PO faisait en quelque sorte allusion à l’accès de l’assemblée par des tiers). Lorsque d'autres font référence à un assembly, Visual Studio doit respecter l'attribut suivant pour qu'il ne s'affiche pas dans intellisense (caché, mais peut TOUJOURS être appelé, alors méfiez-vous):

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]

Si vous n'aviez pas d'autre choix, vous devriez pouvoir utiliser new sur une méthode masquant une méthode de type base, renvoyer => throw new NotSupportedException(); et le combiner avec l'attribut ci-dessus.

Une autre astuce consiste à NE PAS hériter d’une classe de base si possible, la base ayant une interface correspondante (telle que IList<T> pour List<T>). L'implémentation "explicitement" d'interfaces masquera également ces méthodes à intellisense sur le type de classe. Par exemple:

public class GoodForNothing: IDisposable
{
    void IDisposable.Dispose() { ... }
}

Dans le cas de var obj = new GoodForNothing(), la méthode Dispose() ne sera pas disponible sur obj. Cependant, il sera accessible à toute personne ayant explicitement transtypé obj à IDisposable.

En outre, vous pouvez également envelopper un type de base au lieu d'en hériter, puis masquer certaines méthodes:

public class MyList<T> : IList<T>
{
    List<T> _Items = new List<T>();
    public T this[int index] => _Items[index];
    public int Count => _Items.Count;
    public void Add(T item) => _Items.Add(item);
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
    void ICollection<T>.Clear() => throw new InvalidOperationException("No you may not!"); // (hidden)
    /*...etc...*/
}
0
James Wilkins

S'ils sont définis comme publics dans la classe d'origine, vous ne pouvez pas les remplacer par privés dans votre classe dérivée. Cependant, vous pouvez faire en sorte que la méthode publique lève une exception et implémente votre propre fonction privée.

Edit: Jorge Ferreira est correct.

0
Cody Brocious