Jak wywołać 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();
    }
}

Wynik to:

Nazywany Special Derived.
Nazywany od Derived. / * to nie jest oczekiwane * /
Zadzwoniono z bazy.

Jak mogę przepisać klasę SpecialDerived, aby metoda klasy średniej „Derived” nie była wywoływana?

AKTUALIZACJA: Powodem, dla którego chcę dziedziczyć po Derived zamiast Base jest klasa pochodna, która zawiera wiele innych implementacji. Ponieważ nie mogę tego zrobić base.base.method(), myślę, że najlepszym sposobem jest wykonanie następujących czynności?

// Nie można zmienić kodu źródłowego

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

Edycja zgodna z aktualizacją.
JoshJordan

Odpowiedzi:


106

Chcę tylko to dodać, ponieważ ludzie nadal wracają do tego pytania nawet po wielu latach. Oczywiście jest to zła praktyka, ale nadal można (w zasadzie) zrobić to, czego chce autor:

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
Naprawdę podoba mi się ta odpowiedź, ponieważ nie chodziło o to, czy jest zalecana, czy nie, czy jest to dobry pomysł, ale pytanie brzmiało, czy istnieje sposób, aby to zrobić, a jeśli tak, to jaki jest.
Shavais

3
Szczególnie przydatne, gdy masz do czynienia z frameworkami / bibliotekami, które nie są rozszerzalne. Na przykład, nigdy nie musiałem uciekać się do takich rozwiązań dla NHibernate, który jest wysoce rozszerzalny. Ale jeśli chodzi o Asp.Net Identity, Entity Framework, Asp.Net Mvc, regularnie używam takich hacków do obsługi ich brakujących funkcji lub zakodowanych zachowań nieodpowiednich dla moich potrzeb.
Frédéric

2
Dziękuję za to! Chciałbym wspomnieć innym, aby przestali cytować wytyczne w odpowiedzi na pytanie. Poproszono o powód, a nie besztanie. Jeśli nie wiesz, nie odpowiadaj! Chciałbym również zachęcić wszystkich do wysadzenia policji szyfrującej, więc może to zniechęci ich do publikowania braku odpowiedzi. Jeśli po udzieleniu odpowiedzi na pytanie poczujesz się zobligowany do przytoczenia wskazówek, nie wahaj się i wspomnij o tym.
DanW

1
A co, jeśli potrzebujemy wartości zwracanej z funkcji bazowej?
Perkins

2
Nieważne, rozgryzłem to. Przesyłaj Func<stuff>zamiastAction
Perkins

92

Jest to zła praktyka programistyczna i niedozwolona w C #. To zła praktyka programistyczna, ponieważ

  • Szczegóły dotyczące głównej bazy są szczegółami implementacji bazy; nie powinieneś na nich polegać. Klasa bazowa dostarcza abstrakcji ponad grandbase; powinieneś używać tej abstrakcji, a nie budować obejścia, aby tego uniknąć.

  • Aby zilustrować konkretny przykład z poprzedniego punktu: jeśli jest to dozwolone, ten wzorzec byłby kolejnym sposobem uczynienia kodu podatnym na awarie klas bazowych. Załóżmy, że Cpochodzi z, z Bktórego pochodzi A. Kod Cużywany base.basedo wywołania metody A. Wtedy autor Bzdaje sobie sprawę, że włożyli zbyt wiele narzędzi do klasy Bi lepszym podejściem jest stworzenie klasy pośredniej, B2która pochodzi Ai Bpochodzi z B2. Po tej zmianie kod w Cwywołuje metodę w B2, a nie w A, ponieważ Cautor przyjął założenie, że szczegóły implementacji B, a mianowicie, że jego bezpośrednią klasą bazową jestA, nigdy się nie zmieni. Wiele decyzji projektowych w C # ma na celu złagodzenie prawdopodobieństwa różnego rodzaju kruchych awarii bazy; decyzja o uznaniu ich za base.basenielegalną całkowicie zapobiega temu szczególnemu posmakowi tego wzorca niepowodzenia.

  • Wywodzisz się ze swojej bazy, ponieważ lubisz to, co robi i chcesz ją ponownie wykorzystać i rozszerzyć. Jeśli nie podoba ci się to, co robi i chcesz to obejść zamiast pracować z tym, to dlaczego w ogóle z tego wyprowadziłeś? Czerp sam z siebie, jeśli jest to funkcja, którą chcesz wykorzystać i rozszerzyć.

  • Baza może wymagać pewnych niezmienników dla celów bezpieczeństwa lub spójności semantycznej, które są utrzymywane przez szczegóły dotyczące sposobu, w jaki baza używa metod grandbase. Zezwolenie klasie pochodnej bazy na pominięcie kodu, który utrzymuje te niezmienniki, może wprowadzić bazę w niespójny, uszkodzony stan.


4
@Jadoon: Zatem wolę kompozycję od dziedziczenia. Twoja klasa może przyjąć instancję Base lub GrandBase, a następnie klasa może odroczyć funkcjonalność instancji.
Eric Lippert

4
@BlackOverlord: Skoro czujesz się mocno związany z tym tematem, dlaczego nie napisać własnej odpowiedzi na to pytanie od siedmiu lat? W ten sposób wszyscy skorzystamy z Twojej mądrości na ten temat, a Ty napisałbyś dwie odpowiedzi na StackOverflow, podwajając swój całkowity wkład. To wygrana-wygrana.
Eric Lippert

4
@Eric Lippert: Są dwa powody, dla których nie napisałem własnej odpowiedzi: po pierwsze, nie wiedziałem, jak to zrobić, dlatego trafiłem na ten temat. Po drugie, na tej stronie znajduje się wyczerpująca odpowiedź Evka.
BlackOverlord

3
@DanW: Znam odpowiedź; to jest pierwsze zdanie mojej odpowiedzi: pożądana funkcja nie jest dozwolona w C #, ponieważ jest to zła praktyka programistyczna . Jak to się robi w C #? Ty nie. Pomysł, że mogę nie znać odpowiedzi na to pytanie, jest zabawny, ale zostawimy to bez dalszych komentarzy. Jeśli uznasz tę odpowiedź za niezadowalającą, dlaczego nie napisz własnej odpowiedzi, która Twoim zdaniem działa lepiej? W ten sposób wszyscy uczymy się na podstawie Twojej mądrości i doświadczenia, a Ty również podwoisz liczbę odpowiedzi, które opublikowałeś w tym roku.
Eric Lippert,

11
@EricLippert a co, jeśli kod bazowy jest wadliwy i musi zostać zastąpiony jak kontrola strony trzeciej? A realizacja obejmuje wezwanie do dziadka? W przypadku aplikacji w świecie rzeczywistym może to mieć charakter krytyczny i wymagać poprawek, więc oczekiwanie na dostawcę zewnętrznego może nie być opcją. Zła praktyka a rzeczywistość środowisk produkcyjnych.
Shiv

22

Nie możesz z C #. W IL jest to faktycznie obsługiwane. Możesz zadzwonić non-virt do którejkolwiek ze swoich klas rodzicielskich ... ale nie rób tego. :)


11

Odpowiedź (o której wiem, że nie jest tym, czego szukasz) brzmi:

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

Prawda jest taka, że ​​masz bezpośrednią interakcję tylko z klasą, z której dziedziczysz. Pomyśl o tej klasie jak o warstwie - dostarczającej tyle lub tak mało jej lub funkcji rodzica, ile sobie tego życzy, do klas pochodnych.

EDYTOWAĆ:

Twoja edycja działa, ale myślę, że użyłbym czegoś takiego:

class Derived : Base
{
    protected bool _useBaseSay = false;

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

Oczywiście w prawdziwej implementacji możesz zrobić coś podobnego dla rozszerzalności i łatwości konserwacji:

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

Następnie klasy pochodne mogą odpowiednio kontrolować stan swoich rodziców.


3
Dlaczego po prostu nie napisać chronionej funkcji, w Derivedktórej wywołania Base.Say, aby można ją było wywołać SpecialDerived? Prostsze, nie?
nawfal

7

Dlaczego po prostu nie rzucić klasy potomnej na określoną klasę nadrzędną i nie wywołać wtedy konkretnej implementacji? Jest to sytuacja szczególna i należy zastosować rozwiązanie dla przypadku specjalnego. Będziesz jednak musiał użyć newsłowa kluczowego w metodach dzieci.

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
Jeśli masz silnik, który nie może wiedzieć o bazie lub dziecku, a mówienie musi działać poprawnie, gdy jest wywoływany przez ten silnik, mów musi być zastąpieniem, a nie nowym. Jeśli dziecko potrzebuje 99% funkcjonalności bazy, ale w jednym przypadku, potrzebuje funkcjonalności superbase ... to jest sytuacja, o której rozumiem, że OP ma mówić, w takim przypadku ta metoda nie zadziała. Nie jest to rzadkie, a obawy dotyczące bezpieczeństwa, które spowodowały zachowanie C #, zwykle nie są tak naprawdę bardzo niepokojące.
Shavais,

Tylko uwaga w przypadku kontrolek i łańcuchów wywołań zdarzeń, metody są często chronione i dlatego nie są dostępne w ten sposób.
Shiv

2
To nie jest użycie dziedziczenia, równie dobrze możesz nadać każdemu Speakowi całkowicie unikalną nazwę.
Nick Sotiros

tak, to nie jest 100% dziedziczenie, ale używa interfejsu od rodzica
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

Możesz również utworzyć prostą funkcję w klasie pochodnej pierwszego poziomu, aby wywołać funkcję grand base


Dokładnie tak, a to zachowuje cały schemat abstrakcji, o który wszyscy tak bardzo się martwią, i podkreśla, że ​​schematy abstrakcji są czasami bardziej kłopotliwe niż warte.
Shavais,

3

Moim 2c w tym celu jest zaimplementowanie funkcjonalności, której potrzebujesz, aby zostać wywołanym w klasie zestawu narzędzi i wywołać ją z dowolnego miejsca:

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

Wymaga to przemyślenia, aby uzyskać uprawnienia dostępu, może być konieczne dodanie niektórych internalmetod dostępu, aby ułatwić funkcjonalność.


1

W przypadkach, w których nie masz dostępu do źródła klasy pochodnej, ale potrzebujesz całego źródła klasy pochodnej oprócz bieżącej metody, zalecałbym również wykonanie klasy pochodnej i wywołanie implementacji klasy pochodnej.

Oto przykład:

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

Jak widać z poprzednich postów, można argumentować, że jeśli trzeba obejść funkcjonalność klas, to coś jest nie tak w architekturze klas. To może być prawda, ale nie zawsze można zrestrukturyzować lub zmienić strukturę klas w dużym, dojrzałym projekcie. Różne poziomy zarządzania zmianami mogą stanowić jeden problem, ale utrzymanie istniejącej funkcjonalności działającej tak samo po refaktoryzacji nie zawsze jest zadaniem trywialnym, zwłaszcza jeśli obowiązują ograniczenia czasowe. W dojrzałym projekcie powstrzymanie różnych testów regresji przed przejściem po restrukturyzacji kodu może być dużym przedsięwzięciem; często pojawiają się niejasne „dziwactwa”. Mieliśmy podobny problem w niektórych przypadkach dziedziczona funkcjonalność nie powinna być wykonywana (lub powinna wykonywać coś innego). Podejście, które zastosowaliśmy poniżej, polegało na umieszczeniu kodu podstawowego, który należy wykluczyć, w oddzielnej funkcji wirtualnej. Tę funkcję można następnie przesłonić w klasie pochodnej, a funkcjonalność wykluczyć lub zmienić. W tym przykładzie można zapobiec wyświetlaniu „Tekstu 2” w klasie pochodnej.

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

Wydaje się, że istnieje wiele pytań dotyczących dziedziczenia metody składowej z klasy dziadków, przesłaniania jej w drugiej klasie, a następnie ponownego wywoływania jej metody z klasy wnuków. Dlaczego po prostu nie odziedziczyć członków dziadków aż do wnuków?

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"

Wydaje się proste ... wnuk dziedziczy tutaj metodę dziadków. Pomyśl o tym… w ten sposób „Object” i jego składowe, takie jak ToString (), są dziedziczone do wszystkich klas w C #. Myślę, że Microsoft nie wykonał dobrej roboty w wyjaśnianiu podstawowego dziedziczenia. Zbyt duży nacisk kładzie się na polimorfizm i implementację. Kiedy przeglądam ich dokumentację, nie ma przykładów na tę bardzo podstawową ideę. :(


-2

Jeśli chcesz uzyskać dostęp do danych klasy bazowej, musisz użyć słowa kluczowego „this” lub użyć tego słowa kluczowego jako odniesienia dla klasy.

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;
        }
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.