Comment appeler base.base.method ()?


127
// Cannot change source code
class Base
{
    public virtual void Say()
    {
        Console.WriteLine("Called from Base.");
    }
}

// Cannot change source code
class Derived : Base
{
    public override void Say()
    {
        Console.WriteLine("Called from Derived.");
        base.Say();
    }
}

class SpecialDerived : Derived
{
    public override void Say()
    {
        Console.WriteLine("Called from Special Derived.");
        base.Say();
    }
}

class Program
{
    static void Main(string[] args)
    {
        SpecialDerived sd = new SpecialDerived();
        sd.Say();
    }
}

Le résultat est:

Appelé de Special Derived.
Appelé de dérivé. / * ce n'est pas prévu * /
Appelé depuis la base.

Comment puis-je réécrire la classe SpecialDerived afin que la méthode de la classe moyenne "Derived" ne soit pas appelée?

MISE À JOUR: La raison pour laquelle je veux hériter de Derived au lieu de Base est la classe Derived contient beaucoup d'autres implémentations. Puisque je ne peux pas faire base.base.method()ici, je suppose que la meilleure façon est de faire ce qui suit?

// Impossible de changer le code source

class Derived : Base
{
    public override void Say()
    {
        CustomSay();

        base.Say();
    }

    protected virtual void CustomSay()
    {
        Console.WriteLine("Called from Derived.");
    }
}

class SpecialDerived : Derived
{
    /*
    public override void Say()
    {
        Console.WriteLine("Called from Special Derived.");
        base.Say();
    }
    */

    protected override void CustomSay()
    {
        Console.WriteLine("Called from Special Derived.");
    }
}

Modification pour correspondre à votre mise à jour.
JoshJordan

Réponses:


106

Je veux juste ajouter ceci ici, car les gens reviennent encore à cette question même après plusieurs fois. Bien sûr, c'est une mauvaise pratique, mais il est toujours possible (en principe) de faire ce que l'auteur veut avec:

class SpecialDerived : Derived
{
    public override void Say()
    {
        Console.WriteLine("Called from Special Derived.");
        var ptr = typeof(Base).GetMethod("Say").MethodHandle.GetFunctionPointer();            
        var baseSay = (Action)Activator.CreateInstance(typeof(Action), this, ptr);
        baseSay();            
    }
}

46
J'aime vraiment cette réponse parce que la question n'était pas de savoir si elle est recommandée ou non ou si c'est une bonne idée ou non, la question était de savoir s'il y avait ou non un moyen de le faire et, dans l'affirmative, de quelle manière.
Shavais le

3
Particulièrement utile lorsque vous devez gérer des frameworks / bibliothèques manquant d'extensibilité. Par exemple, je n'avais jamais eu besoin de recourir à de telles solutions pour NHibernate qui est très extensible. Mais pour traiter avec Asp.Net Identity, Entity Framework, Asp.Net Mvc, je finis régulièrement par utiliser de tels hacks pour gérer leurs fonctionnalités manquantes ou des comportements codés en dur inadaptés à mes besoins.
Frédéric

2
Merci pour ça! Je voudrais demander aux autres de cesser de citer des lignes directrices en réponse à une question. On lui a demandé une raison, pas une réprimande. Si vous ne savez pas, ne répondez pas! Je voudrais également encourager tout le monde à faire sauter le code-police, alors peut-être que cela les découragera de publier des non-réponses. Si, après avoir répondu à une question, vous vous sentez obligé de citer des lignes directrices, n'hésitez pas à le mentionner.
DanW

1
Et si nous avons besoin de la valeur de retour de la fonction sous-jacente?
Perkins

2
Nevermind, je l'ai compris. Au Func<stuff>lieu deAction
Perkins

92

Ceci est une mauvaise pratique de programmation et n'est pas autorisé en C #. C'est une mauvaise pratique de programmation car

  • Les détails de la base sont les détails de mise en œuvre de la base; vous ne devriez pas compter sur eux. La classe de base fournit une abstraction par-dessus la grande base; vous devriez utiliser cette abstraction, pas construire un contournement pour l'éviter.

  • Pour illustrer un exemple spécifique du point précédent: s'il est autorisé, ce modèle serait encore une autre façon de rendre le code sensible aux échecs de classe de base fragile. Supposons que Cdérive de Bqui dérive de A. Le code Cutilise base.basepour appeler une méthode de A. Ensuite, l'auteur de se Brend compte qu'il a mis trop de matériel en classe B, et une meilleure approche consiste à créer une classe intermédiaire B2qui dérive de Aet Bdérive de B2. Après ce changement, le code in Cappelle une méthode in B2, pas in A, car Cl'auteur de a fait l'hypothèse que les détails d'implémentation de B, à savoir que sa classe de base directe estA, ne changerait jamais. De nombreuses décisions de conception en C # sont d'atténuer la probabilité de divers types de défaillances de base fragile; la décision de rendre base.baseillégal empêche entièrement cette saveur particulière de ce modèle d'échec.

  • Vous êtes dérivé de votre base parce que vous aimez ce qu'elle fait et que vous souhaitez la réutiliser et l'étendre. Si vous n'aimez pas ce qu'il fait et que vous voulez le contourner plutôt que de travailler avec, alors pourquoi en avez-vous dérivé en premier lieu? Dérivez vous-même de la grande base si c'est la fonctionnalité que vous souhaitez utiliser et étendre.

  • La base peut nécessiter certains invariants pour des raisons de sécurité ou de cohérence sémantique qui sont maintenus par les détails de la façon dont la base utilise les méthodes de la base grand. Permettre à une classe dérivée de la base d'ignorer le code qui maintient ces invariants pourrait mettre la base dans un état incohérent et corrompu.


4
@Jadoon: Préférez alors la composition à l'héritage. Votre classe peut prendre une instance de Base ou GrandBase, et la classe peut alors différer la fonctionnalité à l'instance.
Éric Lippert

4
@BlackOverlord: Puisque vous vous sentez fortement sur ce sujet, pourquoi ne pas écrire votre propre réponse à cette question vieille de sept ans? De cette façon, nous bénéficions tous de votre sagesse sur ce sujet, et vous auriez alors écrit deux réponses sur StackOverflow, doublant ainsi votre contribution totale. C'est gagnant-gagnant.
Eric Lippert

4
@Eric Lippert: Il y a deux raisons pour lesquelles je n'ai pas écrit ma propre réponse: d'abord, je ne savais pas comment le faire, c'est pourquoi j'ai trouvé ce sujet. Deuxièmement, il y a une réponse complète d'Evk en bas de cette page.
BlackOverlord

3
@DanW: Je connais la réponse; c'est la première phrase de ma réponse: la fonctionnalité souhaitée n'est pas autorisée en C # car c'est une mauvaise pratique de programmation . Comment faites-vous cela en C #? Vous ne le faites pas. L'idée que je ne connais peut-être pas la réponse à cette question est amusante, mais nous laisserons cela passer sans autre commentaire. Maintenant, si vous trouvez cette réponse insatisfaisante, pourquoi ne pas écrire votre propre réponse qui, selon vous, fait un meilleur travail? De cette façon, nous apprenons tous de votre sagesse et de votre expérience, et vous doubleriez également le nombre de réponses que vous avez publiées cette année.
Eric Lippert

11
@EricLippert et si le code de base est défectueux et doit être remplacé comme un contrôle tiers? Et la mise en œuvre comprend un appel à grandbase? Pour les applications du monde réel, il peut être de nature critique et nécessiter un correctif.Attendre le fournisseur tiers peut donc ne pas être une option. Mauvaise pratique vs réalité des environnements de production.
Shiv

22

Vous ne pouvez pas à partir de C #. Depuis IL, cela est en fait pris en charge. Vous pouvez faire un appel non-virt à l'une de vos classes parentales ... mais ne le faites pas. :)


11

La réponse (dont je sais que ce n'est pas ce que vous cherchez) est:

class SpecialDerived : Base
{
    public override void Say()
    {
        Console.WriteLine("Called from Special Derived.");
        base.Say();
    }
}

La vérité est que vous n'avez qu'une interaction directe avec la classe dont vous héritez. Considérez cette classe comme une couche - fournissant autant ou aussi peu de fonctionnalités de celle-ci ou de son parent qu'il le souhaite à ses classes dérivées.

ÉDITER:

Votre modification fonctionne, mais je pense que j'utiliserais quelque chose comme ceci:

class Derived : Base
{
    protected bool _useBaseSay = false;

    public override void Say()
    {
        if(this._useBaseSay)
            base.Say();
        else
            Console.WriteLine("Called from Derived");
    }
}

Bien sûr, dans une implémentation réelle, vous pouvez faire quelque chose de plus comme ceci pour l'extensibilité et la maintenabilité:

class Derived : Base
{
    protected enum Mode
    {
        Standard,
        BaseFunctionality,
        Verbose
        //etc
    }

    protected Mode Mode
    {
        get; set;
    }

    public override void Say()
    {
        if(this.Mode == Mode.BaseFunctionality)
            base.Say();
        else
            Console.WriteLine("Called from Derived");
    }
}

Ensuite, les classes dérivées peuvent contrôler l'état de leurs parents de manière appropriée.


3
Pourquoi ne pas simplement écrire une fonction protégée dans Derivedlaquelle des appels Base.Say, afin de pouvoir l'appeler SpecialDerived? Plus simple, non?
nawfal

7

Pourquoi ne pas simplement convertir la classe enfant en une classe parente spécifique et appeler alors l'implémentation spécifique? Il s'agit d'une situation de cas particulier et une solution de cas particulier doit être utilisée. Vous devrez cependant utiliser le newmot - clé dans les méthodes enfants.

public class SuperBase
{
    public string Speak() { return "Blah in SuperBase"; }
}

public class Base : SuperBase
{
    public new string Speak() { return "Blah in Base"; }
}

public class Child : Base
{
    public new string Speak() { return "Blah in Child"; }
}

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Child childObj = new Child();

        Console.WriteLine(childObj.Speak());

        // casting the child to parent first and then calling Speak()
        Console.WriteLine((childObj as Base).Speak()); 

        Console.WriteLine((childObj as SuperBase).Speak());
    }
}

2
Si vous avez un moteur qui ne peut pas connaître la base ou l'enfant, et que la parole doit fonctionner correctement lorsqu'elle est appelée par ce moteur, la parole doit être une priorité, pas une nouvelle. Si l'enfant a besoin de 99% des fonctionnalités de la base, mais dans le cas unique, il a besoin de la fonctionnalité de superbase ... c'est le genre de situation dont je comprends que l'OP parle, auquel cas cette méthode ne fonctionnera pas. Ce n'est pas rare et les problèmes de sécurité qui ont donné lieu au comportement de C # ne sont généralement pas vraiment très préoccupants.
Shavais le

Juste une note dans le cas des contrôles et des chaînes d'appels d'événements, les méthodes sont souvent protégées et donc pas accessibles comme ça.
Shiv

2
Cela n'utilise pas l'héritage, vous pouvez également donner à chaque Speak un nom entièrement unique.
Nick Sotiros

oui ce n'est pas un héritage à 100% mais il utilise l'interface du parent
Kruczkowski

5
public class A
{
    public int i = 0;
    internal virtual void test()
    {
        Console.WriteLine("A test");
    }
}

public class B : A
{
    public new int i = 1;
    public new void test()
    {
        Console.WriteLine("B test");
    }
}

public class C : B
{
    public new int i = 2;
    public new void test()
    {
        Console.WriteLine("C test - ");
        (this as A).test(); 
    }
}

4

Vous pouvez également créer une fonction simple dans la classe dérivée de premier niveau, pour appeler la fonction de base principale


Exactement, et cela préserve l'ensemble du schéma d'abstraction qui préoccupe tout le monde, et cela met en évidence la façon dont les schémas d'abstraction sont parfois plus problématiques qu'ils n'en valent la peine.
Shavais le

3

Mon 2c pour cela est d'implémenter la fonctionnalité dont vous avez besoin pour être appelé dans une classe de boîte à outils et de l'appeler de partout où vous en avez besoin:

// Util.cs
static class Util 
{
    static void DoSomething( FooBase foo ) {}
}

// FooBase.cs
class FooBase
{
    virtual void Do() { Util.DoSomething( this ); }
}


// FooDerived.cs
class FooDerived : FooBase
{
    override void Do() { ... }
}

// FooDerived2.cs
class FooDerived2 : FooDerived
{
    override void Do() { Util.DoSomething( this ); }
}

Cela nécessite une réflexion sur les privilèges d'accès, vous devrez peut-être ajouter des internalméthodes d' accès pour faciliter la fonctionnalité.


1

Dans les cas où vous n'avez pas accès à la source de la classe dérivée, mais que vous avez besoin de toute la source de la classe dérivée en plus de la méthode actuelle, je vous recommande également de créer une classe dérivée et d'appeler l'implémentation de la classe dérivée.

Voici un exemple:

//No access to the source of the following classes
public class Base
{
     public virtual void method1(){ Console.WriteLine("In Base");}
}
public class Derived : Base
{
     public override void method1(){ Console.WriteLine("In Derived");}
     public void method2(){ Console.WriteLine("Some important method in Derived");}
}

//Here should go your classes
//First do your own derived class
public class MyDerived : Base
{         
}

//Then derive from the derived class 
//and call the bass class implementation via your derived class
public class specialDerived : Derived
{
     public override void method1()
     { 
          MyDerived md = new MyDerived();
          //This is actually the base.base class implementation
          MyDerived.method1();  
     }         
}

0

Comme on peut le voir dans les articles précédents, on peut affirmer que si la fonctionnalité de classe doit être contournée, alors quelque chose ne va pas dans l'architecture de classe. Cela peut être vrai, mais on ne peut pas toujours restructurer ou refactoriser la structure de classe sur un grand projet mature. Les différents niveaux de gestion du changement peuvent être un problème, mais maintenir le même fonctionnement des fonctionnalités existantes après la refactorisation n'est pas toujours une tâche triviale, surtout si des contraintes de temps s'appliquent. Sur un projet mature, il peut être assez difficile d'empêcher divers tests de régression de réussir après une restructuration de code; il y a souvent des «bizarreries» obscures qui apparaissent. Nous avons eu un problème similaire dans certains cas, les fonctionnalités héritées ne devraient pas s'exécuter (ou devraient effectuer autre chose). L'approche que nous avons suivie ci-dessous, était de mettre le code de base à exclure dans une fonction virtuelle distincte. Cette fonction peut ensuite être remplacée dans la classe dérivée et la fonctionnalité exclue ou modifiée. Dans cet exemple, "Texte 2" peut être empêché de sortir dans la classe dérivée.

public class Base
{
    public virtual void Foo()
    {
        Console.WriteLine("Hello from Base");
    }
}

public class Derived : Base
{
    public override void Foo()
    {
        base.Foo();
        Console.WriteLine("Text 1");
        WriteText2Func();
        Console.WriteLine("Text 3");
    }

    protected virtual void WriteText2Func()
    {  
        Console.WriteLine("Text 2");  
    }
}

public class Special : Derived
{
    public override void WriteText2Func()
    {
        //WriteText2Func will write nothing when 
        //method Foo is called from class Special.
        //Also it can be modified to do something else.
    }
}

0

Il semble y avoir beaucoup de ces questions concernant l'héritage d'une méthode membre d'une classe Grand-parent, la substituant dans une seconde classe, puis l'appel à sa méthode à partir d'une classe Petit-enfant. Pourquoi ne pas simplement hériter des membres des grands-parents jusqu'aux petits-enfants?

class A
{
    private string mystring = "A";    
    public string Method1()
    {
        return mystring;
    }
}

class B : A
{
    // this inherits Method1() naturally
}

class C : B
{
    // this inherits Method1() naturally
}


string newstring = "";
A a = new A();
B b = new B();
C c = new C();
newstring = a.Method1();// returns "A"
newstring = b.Method1();// returns "A"
newstring = c.Method1();// returns "A"

Cela semble simple ... le petit-enfant hérite de la méthode des grands-parents ici. Pensez-y ... c'est ainsi que "Object" et ses membres comme ToString () sont hérités de toutes les classes en C #. Je pense que Microsoft n'a pas fait un bon travail pour expliquer l'héritage de base. L'accent est trop mis sur le polymorphisme et la mise en œuvre. Quand je fouille dans leur documentation, il n'y a pas d'exemples de cette idée très basique. :(


-2

Si vous souhaitez accéder aux données de classe de base, vous devez utiliser le mot-clé "this" ou vous utilisez ce mot-clé comme référence pour la classe.

namespace thiskeyword
{
    class Program
    {
        static void Main(string[] args)
        {
            I i = new I();
            int res = i.m1();
            Console.WriteLine(res);
            Console.ReadLine();
        }
    }

    public class E
    {
        new public int x = 3;
    }

    public class F:E
    {
        new public int x = 5;
    }

    public class G:F
    {
        new public int x = 50;
    }

    public class H:G
    {
        new public int x = 20;
    }

    public class I:H
    {
        new public int x = 30;

        public int m1()
        {
           // (this as <classname >) will use for accessing data to base class

            int z = (this as I).x + base.x + (this as G).x + (this as F).x + (this as E).x; // base.x refer to H
            return z;
        }
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.