Come chiamare 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();
    }
}

Il risultato è:

Chiamato da Special Derived.
Chiamato da Derived. / * questo non è previsto * /
Chiamato dalla base.

Come posso riscrivere la classe SpecialDerived in modo che il metodo "Derived" della classe media non venga chiamato?

AGGIORNAMENTO: Il motivo per cui voglio ereditare da Derived invece che da Base è la classe Derived che contiene molte altre implementazioni. Dato che non posso fare base.base.method()qui, immagino che il modo migliore sia fare quanto segue?

// Impossibile modificare il codice sorgente

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

Modifica in base al tuo aggiornamento.
JoshJordan

Risposte:


106

Voglio solo aggiungere questo qui, poiché le persone tornano ancora su questa domanda anche dopo molto tempo. Ovviamente è una cattiva pratica, ma è ancora possibile (in linea di principio) fare ciò che l'autore vuole con:

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
Mi piace molto questa risposta perché la domanda non era se fosse consigliato o meno o se fosse o meno una buona idea, la domanda era se ci fosse o meno un modo per farlo e, in caso affermativo, quale fosse.
Shavais

3
Particolarmente utile quando si ha a che fare con framework / librerie privi di estensibilità. Ad esempio, non avevo mai avuto bisogno di ricorrere a tali soluzioni per NHibernate che è altamente estensibile. Ma per occuparmi di Asp.Net Identity, Entity Framework, Asp.Net Mvc, finisco regolarmente per utilizzare tali hack per gestire le loro caratteristiche mancanti o comportamenti hard coded non adatti alle mie esigenze.
Frédéric

2
Grazie per quello! Vorrei menzionare alle altre persone di smettere di citare le linee guida in risposta a una domanda. È stato chiesto un motivo, non un rimprovero. Se non lo sai, non rispondere! Vorrei anche incoraggiare tutti a far saltare la polizia del codice, quindi forse li scoraggerà dal pubblicare non risposte. Se dopo aver risposto a una domanda, ti senti obbligato a citare le linee guida, vai avanti e menzionalo.
DanW

1
E se avessimo bisogno del valore restituito dalla funzione sottostante?
Perkins

2
Non importa, capito. Trasmetti a Func<stuff>invece diAction
Perkins

92

Questa è una cattiva pratica di programmazione e non è consentita in C #. È una cattiva pratica di programmazione perché

  • I dettagli del grandbase sono dettagli di implementazione della base; non dovresti fare affidamento su di loro. La classe base fornisce un'astrazione in cima al grandbase; dovresti usare quell'astrazione, non costruire un bypass per evitarlo.

  • Per illustrare un esempio specifico del punto precedente: se consentito, questo modello sarebbe ancora un altro modo per rendere il codice suscettibile di errori di classe base fragile. Supponiamo Cderivi da Bcui derivi da A. Il codice Cutilizzato base.baseper chiamare un metodo di A. Allora l'autore di si Baccorge di aver messo troppa attrezzatura in classe B, e un approccio migliore è fare classe intermedia B2che deriva da Ae Bda cui deriva B2. Dopo questa modifica, il codice in Csta chiamando un metodo in B2, non in A, perché Cl'autore ha assunto che i dettagli di implementazione B, cioè, che la sua classe di base diretta siaA, non cambierebbe mai. Molte decisioni di progettazione in C # mirano a mitigare la probabilità di vari tipi di errori di base fragili; la decisione di rendere base.baseillegale impedisce completamente questo particolare sapore di quel modello di fallimento.

  • Sei derivato dalla tua base perché ti piace quello che fa e vuoi riutilizzarlo ed estenderlo. Se non ti piace quello che fa e vuoi aggirarlo piuttosto che lavorarci, allora perché l'hai derivato in primo luogo? Deriva tu stesso dal grandbase se questa è la funzionalità che desideri utilizzare ed estendere.

  • La base potrebbe richiedere determinati invarianti per scopi di sicurezza o coerenza semantica che vengono mantenuti dai dettagli di come la base utilizza i metodi della base grande. Consentire a una classe derivata della base di ignorare il codice che mantiene tali invarianti potrebbe porre la base in uno stato incoerente e danneggiato.


4
@ Jadoon: Allora preferisci la composizione all'eredità. La tua classe può prendere un'istanza di Base o GrandBase e la classe può quindi rimandare la funzionalità all'istanza.
Eric Lippert

4
@BlackOverlord: Dal momento che ti senti fortemente su questo argomento, perché non scrivere una tua risposta a questa domanda di sette anni? In questo modo potremo tutti beneficiare della tua saggezza su questo argomento e avresti quindi scritto due risposte su StackOverflow, raddoppiando il tuo contributo totale. È una vittoria per tutti.
Eric Lippert

4
@ Eric Lippert: Ci sono due ragioni per cui non ho scritto la mia risposta: primo, non sapevo come farlo, ecco perché ho trovato questo argomento. In secondo luogo, c'è una risposta esauriente di Evk in questa pagina.
BlackOverlord

3
@ DanW: conosco la risposta; è la prima frase della mia risposta: la funzionalità desiderata non è consentita in C # perché è una cattiva pratica di programmazione . Come si esegue questa operazione in C #? Non lo fai. L'idea che potrei non conoscere la risposta a questa domanda è divertente, ma la lasceremo passare senza ulteriori commenti. Ora, se trovi questa risposta insoddisfacente, perché non scrivere la tua risposta che pensi faccia un lavoro migliore? In questo modo impariamo tutti dalla tua saggezza ed esperienza e raddoppieresti anche il numero di risposte che hai pubblicato quest'anno.
Eric Lippert

11
@EricLippert cosa succede se il codice di base è difettoso e deve essere sovrascritto come un controllo di terze parti? E l'implementazione include una chiamata a grandbase? Per le applicazioni del mondo reale, potrebbe essere di natura critica e richiedere un aggiornamento rapido, quindi l'attesa del fornitore di terze parti potrebbe non essere un'opzione. Cattive pratiche vs realtà degli ambienti di produzione.
Shiv

22

Non puoi da C #. Da IL, questo è effettivamente supportato. Puoi fare una chiamata non-virt a qualsiasi classe dei tuoi genitori ... ma per favore non farlo. :)


11

La risposta (che so non è quella che stai cercando) è:

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

La verità è che hai solo un'interazione diretta con la classe da cui erediti. Pensa a quella classe come a un livello, fornendo tanto o poco di essa o la funzionalità del suo genitore come desidera alle sue classi derivate.

MODIFICARE:

La tua modifica funziona, ma penso che userei qualcosa del genere:

class Derived : Base
{
    protected bool _useBaseSay = false;

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

Ovviamente, in un'implementazione reale, potresti fare qualcosa di più simile per estensibilità e manutenibilità:

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

Quindi, le classi derivate possono controllare lo stato dei loro genitori in modo appropriato.


3
Perché non scrivere semplicemente una funzione protetta in Derivedcui chiama Base.Say, in modo che possa essere chiamata da SpecialDerived? Più semplice, no?
nawfal

7

Perché non eseguire semplicemente il cast della classe figlia su una classe genitore specifica e quindi richiamare l'implementazione specifica? Questa è una situazione di caso speciale e dovrebbe essere utilizzata una soluzione di caso speciale. newTuttavia , dovrai usare la parola chiave nei metodi figli.

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
Se hai un motore che non può conoscere la base o il bambino e il parlato deve funzionare correttamente quando viene chiamato da quel motore, parlare deve essere un override, non un nuovo. Se il bambino ha bisogno del 99% della funzionalità di base, ma in un caso di speak, ha bisogno della funzionalità di superbase ... questo è il tipo di situazione di cui intendo parlare l'OP, nel qual caso questo metodo non funzionerà. Non è raro e i problemi di sicurezza che hanno dato origine al comportamento di C # in genere non sono molto preoccupanti.
Shavais

Solo una nota nel caso di controlli e catene di chiamate di eventi, i metodi sono spesso protetti e quindi non accessibili in questo modo.
Shiv

2
Non si usa l'ereditarietà, puoi anche dare a ogni Speak un nome completamente univoco.
Nick Sotiros

sì, non è un'ereditarietà al 100% ma utilizza l'interfaccia del genitore
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

Puoi anche creare una semplice funzione nella classe derivata di primo livello, per chiamare la funzione di base principale


Esattamente così, e questo preserva l'intero schema di astrazione di cui tutti sono così preoccupati, ed evidenzia il modo in cui gli schemi di astrazione a volte sono più problemi di quanto valgano.
Shavais

3

Il mio 2c per questo è implementare la funzionalità di cui hai bisogno per essere chiamato in una classe del toolkit e chiamarla da dove ti serve:

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

Ciò richiede alcune considerazioni sul privilegio di accesso, potrebbe essere necessario aggiungere alcuni internalmetodi di accesso per facilitare la funzionalità.


1

Nei casi in cui non si ha accesso alla sorgente della classe derivata, ma è necessaria tutta la sorgente della classe derivata oltre al metodo corrente, si consiglia di eseguire anche una classe derivata e chiamare l'implementazione della classe derivata.

Ecco un esempio:

//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

Come si può vedere dai post precedenti, si può sostenere che se la funzionalità della classe deve essere aggirata, allora qualcosa non va nell'architettura della classe. Potrebbe essere vero, ma non è sempre possibile ristrutturare o rifattorizzare la struttura di classe su un grande progetto maturo. I vari livelli di gestione delle modifiche potrebbero essere un problema, ma mantenere le stesse funzionalità esistenti dopo il refactoring non è sempre un'attività banale, soprattutto se si applicano vincoli di tempo. In un progetto maturo può essere piuttosto impegnativo impedire che vari test di regressione vengano superati dopo una ristrutturazione del codice; ci sono spesso oscure "stranezze" che si manifestano. Abbiamo avuto un problema simile in alcuni casi la funzionalità ereditata non doveva essere eseguita (o doveva eseguire qualcos'altro). L'approccio che abbiamo seguito di seguito, era mettere il codice di base che doveva essere escluso in una funzione virtuale separata. Questa funzione può quindi essere sovrascritta nella classe derivata e la funzionalità esclusa o modificata. In questo esempio è possibile impedire l'output di "Testo 2" nella classe derivata.

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

Sembra che ci siano molte di queste domande sull'ereditarietà di un metodo membro da una classe nonni, sovrascrivendola in una seconda classe, quindi chiamando di nuovo il suo metodo da una classe nipote. Perché non ereditare solo i membri dei nonni fino ai nipoti?

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"

Sembra semplice .... qui il nipote eredita il metodo dei nonni. Pensaci ..... è così che "Object" ei suoi membri come ToString () vengono ereditati in tutte le classi in C #. Penso che Microsoft non abbia svolto un buon lavoro nello spiegare l'ereditarietà di base. C'è troppa attenzione sul polimorfismo e sull'implementazione. Quando scavo nella loro documentazione non ci sono esempi di questa idea di base. :(


-2

Se vuoi accedere ai dati della classe base, devi usare la parola chiave "this" oppure utilizzare questa parola chiave come riferimento per 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;
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.