Wie rufe ich base.base.method () auf?

126
// 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();
    }
}

Das Ergebnis ist:

Wird von Special Derived aufgerufen.
Von Derived aufgerufen. / * dies wird nicht erwartet * /
Wird von der Basis aufgerufen.

Wie kann ich die SpecialDerived-Klasse so umschreiben, dass die Methode "Derived" der Mittelklasse nicht aufgerufen wird?

UPDATE: Der Grund, warum ich von Derived anstelle von Base erben möchte, ist, dass die Derived-Klasse viele andere Implementierungen enthält. Da ich base.base.method()hier nicht kann , denke ich, ist der beste Weg, Folgendes zu tun?

// Quellcode kann nicht geändert werden

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.");
    }
}
AZ.
quelle
Bearbeitung entsprechend Ihrem Update.
JoshJordan

Antworten:

106

Ich möchte dies hier nur hinzufügen, da die Leute auch nach langer Zeit noch auf diese Frage zurückkommen. Natürlich ist es eine schlechte Praxis, aber es ist (im Prinzip) immer noch möglich, das zu tun, was der Autor will:

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();            
    }
}
Evk
quelle
46
Ich mag diese Antwort wirklich, weil die Frage nicht war, ob sie empfohlen wird oder ob es eine gute Idee ist oder nicht, sondern ob es einen Weg gibt, dies zu tun, und wenn ja, wie dieser ist.
Shavais
3
Besonders nützlich, wenn Sie sich mit Frameworks / Bibliotheken befassen müssen, denen die Erweiterbarkeit fehlt. Zum Beispiel musste ich nie auf solche Lösungen für NHibernate zurückgreifen, die sehr erweiterbar sind. Für den Umgang mit Asp.Net Identity, Entity Framework und Asp.Net Mvc verwende ich jedoch regelmäßig solche Hacks, um ihre fehlenden Funktionen oder fest codierten Verhaltensweisen zu behandeln, die für meine Anforderungen ungeeignet sind.
Frédéric
2
Dank dafür! Ich möchte den anderen Leuten gegenüber erwähnen, dass sie keine Richtlinien mehr als Antwort auf eine Frage zitieren sollen. Es wurde nach einem Grund gefragt, nicht nach einer Schelte. Wenn Sie es nicht wissen, antworten Sie nicht! Ich möchte auch alle dazu ermutigen, die Code-Polizei zu sprengen, damit sie möglicherweise davon abgehalten werden, keine Antworten zu veröffentlichen. Wenn Sie nach der Beantwortung einer Frage gezwungen sind, Richtlinien zu zitieren, erwähnen Sie diese.
DanW
1
Was ist, wenn wir den Rückgabewert der zugrunde liegenden Funktion benötigen?
Perkins
2
Vergiss es, fand es heraus. Besetzung Func<stuff>stattAction
Perkins
92

Dies ist eine schlechte Programmierpraxis und in C # nicht zulässig. Es ist eine schlechte Programmierpraxis, weil

  • Die Details der Grandbase sind Implementierungsdetails der Basis; Sie sollten sich nicht auf sie verlassen. Die Basisklasse bietet eine Abstraktion über der Basis; Sie sollten diese Abstraktion verwenden und keinen Bypass erstellen, um dies zu vermeiden.

  • Um ein spezifisches Beispiel für den vorherigen Punkt zu veranschaulichen: Wenn dies zulässig ist, wäre dieses Muster eine weitere Möglichkeit, Code für Fehler der spröden Basisklasse anfällig zu machen. Angenommen, Cleitet sich ab, von Bdenen abgeleitet wird A. Code in Cverwendet base.base, um eine Methode von aufzurufen A. Dann Berkennt der Autor von , dass er zu viel Ausrüstung in den Unterricht gesteckt hat B, und ein besserer Ansatz besteht darin, eine Zwischenklasse zu erstellen B2, die von Aund Babgeleitet ist B2. Nach dieser Änderung Cruft Code in eine Methode in B2und nicht in auf A, da Cder Autor davon ausgegangen ist, dass die Implementierungsdetails Bnämlich die direkte Basisklasse sindAwürde sich nie ändern. Viele Entwurfsentscheidungen in C # dienen dazu, die Wahrscheinlichkeit verschiedener Arten von spröden Basisfehlern zu verringern. Die Entscheidung, base.baseillegal zu machen, verhindert diesen besonderen Geschmack dieses Versagensmusters vollständig.

  • Sie haben von Ihrer Basis abgeleitet, weil Ihnen das gefällt, was es tut, und Sie es wiederverwenden und erweitern möchten. Wenn Ihnen das, was es tut, nicht gefällt und Sie es umgehen möchten, anstatt damit zu arbeiten, warum haben Sie es dann überhaupt abgeleitet? Leiten Sie von der Grandbase selbst ab, ob dies die Funktionalität ist, die Sie verwenden und erweitern möchten.

  • Die Basis erfordert möglicherweise bestimmte Invarianten für Sicherheits- oder semantische Konsistenzzwecke, die durch die Details der Verwendung der Methoden der Grandbase durch die Basis aufrechterhalten werden. Wenn eine abgeleitete Klasse der Basis den Code überspringen kann, der diese Invarianten verwaltet, kann die Basis in einen inkonsistenten, beschädigten Zustand versetzt werden.

Eric Lippert
quelle
4
@ Jadoon: Dann lieber Komposition als Vererbung. Ihre Klasse kann eine Instanz von Base oder GrandBase annehmen, und die Klasse kann dann die Funktionalität auf die Instanz verschieben.
Eric Lippert
4
@BlackOverlord: Da Sie sich stark für dieses Thema interessieren, schreiben Sie doch eine eigene Antwort auf diese sieben Jahre alte Frage. Auf diese Weise können wir alle von Ihrer Weisheit zu diesem Thema profitieren, und Sie hätten dann zwei Antworten auf StackOverflow geschrieben, wodurch sich Ihr Gesamtbeitrag verdoppelt hätte. Das ist eine Win-Win-Situation.
Eric Lippert
4
@ Eric Lippert: Es gibt zwei Gründe, warum ich meine eigene Antwort nicht geschrieben habe: Erstens hatte ich nicht gewusst, wie es geht, deshalb habe ich dieses Thema gefunden. Zweitens gibt es eine umfassende Antwort von Evk auf dieser Seite.
BlackOverlord
3
@ DanW: Ich kenne die Antwort; Es ist der erste Satz meiner Antwort: Die gewünschte Funktion ist in C # nicht zulässig, da dies eine schlechte Programmierpraxis ist . Wie macht man das in C #? Das tust du nicht. Die Vorstellung, dass ich die Antwort auf diese Frage vielleicht nicht kenne, ist amüsant, aber wir werden das ohne weiteren Kommentar passieren lassen. Wenn Sie diese Antwort als unbefriedigend empfinden, schreiben Sie doch Ihre eigene Antwort, die Ihrer Meinung nach einen besseren Job macht. Auf diese Weise lernen wir alle aus Ihrer Weisheit und Erfahrung, und Sie würden auch die Anzahl der Antworten, die Sie in diesem Jahr gepostet haben, verdoppeln.
Eric Lippert
11
@EricLippert Was ist, wenn der Basiscode fehlerhaft ist und wie ein Steuerelement eines Drittanbieters überschrieben werden muss? Und die Implementierung beinhaltet einen Aufruf an Grandbase? Für reale Anwendungen kann es kritischer Natur sein und Hotfixing erfordern, sodass das Warten auf den Drittanbieter möglicherweise keine Option ist. Schlechte Praxis gegen die Realität von Produktionsumgebungen.
Shiv
22

Sie können nicht von C #. Von IL wird dies tatsächlich unterstützt. Sie können jede Ihrer Elternklassen nicht virtuell anrufen ... aber bitte nicht. :) :)

rh.
quelle
11

Die Antwort (von der ich weiß, dass sie nicht das ist, wonach Sie suchen) lautet:

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

Die Wahrheit ist, dass Sie nur eine direkte Interaktion mit der Klasse haben, von der Sie erben. Stellen Sie sich diese Klasse als eine Ebene vor, die ihren abgeleiteten Klassen so viel oder so wenig davon oder die Funktionalität ihrer Eltern zur Verfügung stellt, wie sie möchte.

BEARBEITEN:

Ihre Bearbeitung funktioniert, aber ich denke, ich würde so etwas verwenden:

class Derived : Base
{
    protected bool _useBaseSay = false;

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

In einer realen Implementierung können Sie natürlich aus Gründen der Erweiterbarkeit und Wartbarkeit etwas Ähnliches tun:

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");
    }
}

Abgeleitete Klassen können dann den Zustand ihrer Eltern angemessen steuern.

JoshJordan
quelle
3
Warum nicht einfach eine geschützte Funktion schreiben, in Derivedder Base.Sayaufgerufen wird, damit sie aufgerufen werden kann SpecialDerived? Einfacher, nein?
Nawfal
7

Warum nicht einfach die untergeordnete Klasse in eine bestimmte übergeordnete Klasse umwandeln und dann die bestimmte Implementierung aufrufen? Dies ist eine Sonderfallsituation, und es sollte eine Sonderfalllösung verwendet werden. Sie müssen das newSchlüsselwort jedoch in den untergeordneten Methoden verwenden.

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());
    }
}
Kruczkowski
quelle
2
Wenn Sie eine Engine haben, die nichts über Basis oder Kind weiß und sprechen muss, wenn sie von dieser Engine aufgerufen wird, muss Sprechen eine Überschreibung sein, keine neue. Wenn ein Kind 99% der Funktionalität der Basis benötigt, aber in einem Fall die Funktionalität der Superbase benötigt ... das ist die Situation, über die ich das OP verstehe. In diesem Fall funktioniert diese Methode nicht. Dies ist nicht ungewöhnlich und die Sicherheitsbedenken, die zu C #s Verhalten geführt haben, sind im Allgemeinen nicht wirklich besorgniserregend.
Shavais
Nur ein Hinweis bei Steuerelementen und Ereignisaufrufketten: Die Methoden sind häufig geschützt und daher nicht so zugänglich.
Shiv
2
Hierbei wird keine Vererbung verwendet. Sie können jedem Speak auch einen eindeutigen Namen geben.
Nick Sotiros
Ja, es ist keine 100% ige Vererbung, aber es verwendet die Schnittstelle des Elternteils
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(); 
    }
}
Pavel
quelle
3

Sie können auch eine einfache Funktion in einer abgeleiteten Klasse der ersten Ebene erstellen, um die Grand-Base-Funktion aufzurufen

Rajesh
quelle
Genau so, und dies bewahrt das gesamte Abstraktionsschema, um das sich alle Sorgen machen, und es zeigt, wie Abstraktionsschemata manchmal mehr Probleme bereiten, als sie wert sind.
Shavais
3

Mein 2c dafür ist, die Funktionalität zu implementieren, die Sie benötigen, um in einer Toolkit-Klasse aufgerufen zu werden, und diese von jedem Ort aus aufzurufen, den Sie benötigen:

// 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 ); }
}

Dies erfordert einige Überlegungen zum Zugriff auf Berechtigungen. Möglicherweise müssen Sie einige Zugriffsmethoden hinzufügen internal, um die Funktionalität zu vereinfachen.

Julian Gold
quelle
1

In Fällen, in denen Sie keinen Zugriff auf die abgeleitete Klassenquelle haben, aber neben der aktuellen Methode die gesamte Quelle der abgeleiteten Klasse benötigen, würde ich empfehlen, dass Sie auch eine abgeleitete Klasse erstellen und die Implementierung der abgeleiteten Klasse aufrufen.

Hier ist ein Beispiel:

//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();  
     }         
}
yoel halb
quelle
0

Wie aus früheren Beiträgen hervorgeht, kann man argumentieren, dass etwas in der Klassenarchitektur nicht stimmt, wenn die Klassenfunktionalität umgangen werden muss. Das mag wahr sein, aber man kann die Klassenstruktur eines großen, ausgereiften Projekts nicht immer umstrukturieren oder umgestalten. Die verschiedenen Ebenen des Änderungsmanagements mögen ein Problem sein, aber es ist nicht immer eine triviale Aufgabe, vorhandene Funktionen nach dem Refactoring gleich zu halten, insbesondere wenn zeitliche Einschränkungen gelten. Bei einem ausgereiften Projekt kann es ein ziemliches Unterfangen sein, zu verhindern, dass verschiedene Regressionstests nach einer Code-Umstrukturierung bestanden werden. Es gibt oft obskure "Kuriositäten", die auftauchen. Wir hatten ein ähnliches Problem. In einigen Fällen sollte die geerbte Funktionalität nicht ausgeführt werden (oder etwas anderes ausführen). Der Ansatz, den wir unten verfolgten, war es, den Basiscode, der ausgeschlossen werden muss, in eine separate virtuelle Funktion zu setzen. Diese Funktion kann dann in der abgeleiteten Klasse überschrieben und die Funktionalität ausgeschlossen oder geändert werden. In diesem Beispiel kann verhindert werden, dass "Text 2" in der abgeleiteten Klasse ausgegeben wird.

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.
    }
}
Pierre
quelle
0

Es scheint viele dieser Fragen zu geben, eine Mitgliedsmethode von einer Großelternklasse zu erben, sie in einer zweiten Klasse zu überschreiben und ihre Methode dann erneut von einer Enkelklasse aufzurufen. Warum nicht einfach die Mitglieder der Großeltern bis zu den Enkelkindern erben?

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"

Scheint einfach ... das Enkelkind erbt hier die Methode der Großeltern. Denken Sie darüber nach ..... so werden "Object" und seine Mitglieder wie ToString () an alle Klassen in C # vererbt. Ich denke, Microsoft hat die grundlegende Vererbung nicht gut erklärt. Es wird zu viel Wert auf Polymorphismus und Implementierung gelegt. Wenn ich ihre Dokumentation durchforste, gibt es keine Beispiele für diese sehr grundlegende Idee. :((

Stokely
quelle
-2

Wenn Sie auf Basisklassendaten zugreifen möchten, müssen Sie das Schlüsselwort "this" verwenden oder dieses Schlüsselwort als Referenz für die Klasse verwenden.

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;
        }
    }
}
Ankit Ranjan
quelle