Як викликати 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();
    }
}

Результат:

Викликається зі спеціальних похідних.
Подзвонено з похідних. / * цього не очікується * /
Викликано з Бази.

Як я можу переписати клас SpecialDerived, щоб метод середнього класу "Похідний" не викликався?

ОНОВЛЕННЯ: Причина, чому я хочу успадковувати від Derived замість Base, - це отриманий клас містить безліч інших реалізацій. Оскільки я тут не можу зробити base.base.method(), я думаю, найкращий спосіб - це зробити наступне?

// Неможливо змінити вихідний код

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

Редагування відповідно до вашого оновлення.
JoshJordan

Відповіді:


106

Просто хочу тут додати це, оскільки люди все ще повертаються до цього питання навіть через багато часу. Звичайно, це погана практика, але все ж можна (в принципі) робити те, що хоче автор:

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
Мені дуже подобається ця відповідь, тому що питання не було в тому, рекомендується чи ні, чи це хороша ідея чи ні, питання полягало в тому, чи є спосіб зробити це чи ні, і якщо так, яким є такий спосіб?
Шавай

3
Особливо корисно, коли вам доводиться мати справу з рамками / вкладками, які не мають розширення. Наприклад, мені ніколи не потрібно було вдаватися до таких рішень для NHibernate, які є дуже розширюваними. Але для роботи з Asp.Net Identity, Entity Framework, Asp.Net Mvc я регулярно закінчую використовувати такі хаки для обробки їхніх відсутніх особливостей або жорстко кодованої поведінки, непридатних для моїх потреб.
Фредерік

2
Дякую за це! Я хотів би згадати інших людей, щоб перестати цитувати вказівки у відповідь на запитання. Це просили причину, а не лаянку. Якщо ви не знаєте, то не відповідайте на це! Я також хотів би закликати всіх вибухати поліцію з кодом, щоб, можливо, це відштовхне їх від публікації невідповідей. Якщо після відповіді на запитання ви відчуваєте вимушеність цитувати вказівки, то продовжуйте і згадуйте про це.
DanW

1
Що робити, якщо нам потрібно повернути значення від основної функції?
Перкінс

2
Не забудьте, зрозумів це. У ролях Func<stuff>замістьAction
Перкінс

92

Це погана практика програмування і заборонена в C #. Це погана практика програмування, оскільки

  • Деталі великої бази - це деталі реалізації бази; не варто покладатися на них. Базовий клас забезпечує абстракцію поверх великої бази; ви повинні використовувати цю абстракцію, не будуючи обхід, щоб уникнути цього.

  • Щоб проілюструвати конкретний приклад попереднього пункту: якщо це дозволено, ця закономірність буде ще одним способом зробити код сприйнятливим до несправностей базового класу. Припустимо, Cпоходить від Bчого походить A. Код у Cвикористанні base.baseдля виклику методу A. Тоді автор Bрозуміє, що вони поставили занадто багато передач у класі B, і кращим підходом є створення проміжного класу, B2який походить Aі Bпоходить від B2. Після цієї зміни код в Cвикликає метод в B2, а не в A, тому Cщо автор зробив припущення, що деталі реалізації B, а саме, що його прямий базовий клас єA, ніколи не зміниться. Багато дизайнерських рішень в C # - це зменшити ймовірність різного роду крихких базових відмов; рішення про base.baseнезаконність повністю перешкоджає саме цьому аромату цієї схеми невдач.

  • Ви отримали свою базу, тому що вам подобається те, що вона робить, і хочете повторно її використовувати та розширити. Якщо вам не подобається те, що він робить, і хочете обійтися, а не працювати з ним, то чому ви виходили з цього в першу чергу? Отримайте від великої бази, якщо це функціонал, який ви хочете використовувати та розширювати.

  • Базі можуть знадобитися певні інваріанти з метою безпеки або семантичної послідовності, які підтримуються деталями того, як база використовує методи великої бази. Дозволення похідному класу бази пропускати код, який підтримує ці інваріанти, може перевести базу в непослідовний, пошкоджений стан.


4
@Jadoon: Тоді віддайте перевагу композиції перед спадщиною. Ваш клас може взяти екземпляр як Base, так і GrandBase, а потім клас може відкласти функціонал для цього примірника.
Ерік Ліпперт

4
@BlackOverlord: Оскільки ви сильно відчуваєте цю тему, чому б не написати власну відповідь на це запитання семирічки? Таким чином, ми всі отримаємо користь від вашої мудрості з цього приводу, і ви б написали дві відповіді на StackOverflow, подвоюючи ваш загальний внесок. Це безпрограшна робота.
Ерік Ліпперт

4
@Eric Lippert: Є дві причини, чому я не написав власної відповіді: по-перше, я не знав, як це зробити, тому я знайшов цю тему. По-друге, на цій сторінці є вичерпна відповідь.
BlackOverlord

3
@DanW: я знаю відповідь; це перше речення моєї відповіді: потрібна функція не дозволена в C #, оскільки це погана практика програмування . Як це зробити в C #? Ви цього не робите. Думка, що я, можливо, не знаю відповіді на це питання, є кумедною, але ми пропустимо це без додаткових коментарів. Тепер, якщо ви вважаєте, що ця відповідь є незадовільною, чому б не написати свою власну відповідь, яку, на вашу думку, робить краще? Таким чином, ми всі вчимося з вашої мудрості та досвіду, і ви також подвоїли кількість відповідей, які ви опублікували цього року.
Ерік Ліпперт

11
@EricLippert, що робити, якщо базовий код є помилковим і його потрібно скасувати, як сторонній контроль? А реалізація включає в себе виклик до grandbase? Для реальних програм він може мати критичний характер і потребувати виправлення, тому очікування постачальника третьої сторони може бути не можливим. Погана практика проти реальності виробничих середовищ.
Шив

22

Ви не можете з C #. З IL це фактично підтримується. Ви можете зателефонувати в нелінійний дзвінок до будь-якого з батьківських класів ... але, будь ласка, не варто. :)


11

Відповідь (яку я знаю, це не те, що ви шукаєте):

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

Правда, у вас є тільки пряма взаємодія з класом, від якого ви успадковуєте. Подумайте про цей клас як про шар, який забезпечує стільки або мало його функціональності або батьківського функціоналу, скільки він бажає для його похідних класів.

Редагувати:

Ваша редакція працює, але я думаю, я би використав щось подібне:

class Derived : Base
{
    protected bool _useBaseSay = false;

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

Звичайно, в реальній реалізації ви можете зробити щось подібне для розширення та ремонтопридатності:

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

Потім похідні класи можуть належним чином контролювати стан своїх батьків.


3
Чому б просто не записати захищену функцію, в Derivedякій дзвінки Base.Say, щоб її можна було викликати SpecialDerived? Простіше, ні?
nawfal

7

Чому б просто не привласнити дочірній клас до певного батьківського класу і не викликати тоді конкретну реалізацію? Це особлива ситуація, і слід використовувати спеціальне рішення. Вам доведеться використовувати newключове слово в дитячих методах.

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
Якщо у вас є двигун, який не може знати про базу чи дитину, і говорити, що потрібно правильно працювати, коли його викликає цей двигун, розмова повинна бути перекриттям, а не новим. Якщо дитині потрібно 99% базової функціональності, але, в одному випадку, вона потребує функціональності супербази ... саме така ситуація, про яку я розумію, про яку йдеться в ОП, і в цьому випадку цей метод не працює. Це не рідкість, і питання безпеки, які спричинили поведінку C #, зазвичай не дуже стосуються.
Шавай

Лише зауваження, що стосується ланцюгів управління та виклику подій, методи часто захищені, а отже, недоступні.
Шив

2
Це не використовує спадщину, ви також можете дати кожному Speak абсолютно унікальне ім’я.
Нік Сотірос

так, це не 100% спадщина, але він використовує інтерфейс від батьків
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

Ви також можете зробити просту функцію в похідному класі першого рівня, щоб викликати грандіозну базову функцію


Саме так, і це зберігає всю схему абстрагування, про яку всі так хвилюються, і це підкреслює те, як схеми абстрагування часом більше проблем, ніж вони варті.
Шавай

3

Мій 2c для цього полягає в тому, щоб реалізувати функціонал, який потрібно викликати в класі інструментарію, і викликати його звідки завгодно:

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

Для цього потрібна певна думка щодо доступу до привілеїв, можливо, вам потрібно буде додати деякі internalметоди аксесуара для полегшення функціональності.


1

У випадках, коли у вас немає доступу до похідного джерела класу, але вам потрібне все джерело похідного класу, окрім поточного методу, то я б рекомендував вам також зробити похідний клас і викликати реалізацію похідного класу.

Ось приклад:

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

Як видно з попередніх дописів, можна стверджувати, що якщо функціональність класу потрібно обійти, то в архіві класу щось не так. Це може бути правдою, але не можна завжди реструктурувати чи переробляти структуру класів на великому зрілому проекті. Різні рівні управління змінами можуть бути однією проблемою, але зберегти існуючу функціональність однаковою після рефакторингу - це не завжди тривіальна задача, особливо якщо застосовуються часові обмеження. У зрілому проекті може бути дуже зобов'язана утримати різні регресійні тести після проходження після реструктуризації коду; часто виникають незрозумілі «дивацтва», які проявляються. У нас була подібна проблема, в деяких випадках успадкована функціональність не повинна виконуватись (або повинна виконувати щось інше). Підхід, який ми дотримувались нижче, було поставити базовий код, який потрібно виключити в окрему віртуальну функцію. Потім цю функцію можна перекрити у похідному класі, а функціональність виключити або змінити. У цьому прикладі "Текст 2" можна запобігти виведенню у похідному класі.

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

Здається, багато цих питань стосується успадкування методу-члена від класу Дідусь, переосмислення його у другому класі, а потім виклику його методу знову з класу онуків. Чому б просто не успадкувати членів бабусі і дідуся аж до онуків?

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"

Здається, просто .... онук успадковує тут метод бабусь і дідусів. Подумайте про це ..... ось так "Об'єкт" та його члени, як ToString () успадковуються до всіх класів у C #. Я думаю, що Microsoft не зробив належної роботи з пояснення основної спадщини. Занадто багато уваги на поліморфізмі та реалізації. Коли я переглядаю їх документацію, немає прикладів цієї дуже основної ідеї. :(


-2

Якщо ви хочете отримати доступ до даних базового класу, ви повинні використовувати ключове слово "це" або використовувати це ключове слово як орієнтир для класу.

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;
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.