Как вызвать 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();
    }
}

Результат:

Вызывается из Special Derived.
Вызывается из Derived. / * этого не ожидается * /
Вызывается с базы.

Как я могу переписать класс SpecialDerived так, чтобы метод среднего класса "Derived" не вызывался?

ОБНОВЛЕНИЕ: причина, по которой я хочу унаследовать от Derived вместо Base, - это класс Derived, который содержит множество других реализаций. Поскольку я не могу сделать 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.");
    }
}

Редактирование в соответствии с вашим обновлением.
Джош Джордан,

Ответы:


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 я регулярно использую такие хаки для обработки их недостающих функций или жестко запрограммированного поведения, неподходящего для моих нужд.
Frédéric

2
Спасибо за это! Я хотел бы попросить других прекратить цитировать инструкции в ответ на вопрос. Спросили по причине, а не порицать. Если не знаете, не отвечайте! Я также хотел бы призвать всех взорвать кодовую полицию, чтобы, возможно, это помешало им публиковать ответы, не отвечающие. Если после ответа на вопрос вы чувствуете себя обязанным процитировать рекомендации, укажите это.
DanW

1
Что, если нам нужно возвращаемое значение из базовой функции?
Perkins

2
Да ладно, разобрался. В ролях Func<stuff>вместоAction
Perkins

92

Это плохая практика программирования и недопустима в C #. Это плохая практика программирования, потому что

  • Детали grandbase - это детали реализации базы; на них не стоит полагаться. Базовый класс предоставляет абстракцию поверх grandbase; вы должны использовать эту абстракцию, а не строить обходной путь, чтобы избежать этого.

  • Чтобы проиллюстрировать конкретный пример предыдущего пункта: если он разрешен, этот шаблон будет еще одним способом сделать код уязвимым для сбоев хрупкого базового класса. Предположим Cпроизводное от Bкоторого производное A. Код в Cиспользовании base.baseдля вызова метода A. Затем автор Bпонимает, что они вложили в класс слишком много снаряжения B, и что лучший подход - создать промежуточный класс, B2который является производным Aи Bпроизводным от B2. После этого изменения код в Cвызывает метод в B2, а не в A, потому что Cавтор сделал предположение, что детали реализации B, а именно, что его прямой базовый классA, никогда не изменится. Многие проектные решения в C # призваны снизить вероятность различных видов хрупких отказов основания; решение сделать base.baseнезаконным полностью предотвращает эту особую разновидность паттерна отказа.

  • Вы заимствовали свою базу, потому что вам нравится то, что она делает, и вы хотите ее повторно использовать и расширять. Если вам не нравится то, что он делает, и вы хотите обойти это, а не работать с этим, то почему вы вообще взяли это? Сами извлеките из grandbase те функции, которые вы хотите использовать и расширить.

  • Базе могут потребоваться определенные инварианты для целей безопасности или семантической согласованности, которые поддерживаются деталями того, как база использует методы большой базы. Разрешение производному классу базы пропускать код, поддерживающий эти инварианты, может привести базу в несогласованное, поврежденное состояние.


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

4
@BlackOverlord: Поскольку вы твердо придерживаетесь этой темы, почему бы не написать собственный ответ на этот вопрос семилетней давности? Таким образом, мы все извлечем пользу из вашей мудрости по этому вопросу, и тогда вы бы написали два ответа на StackOverflow, удвоив свой общий вклад. Это беспроигрышный вариант.
Эрик Липперт

4
@ Эрик Липперт: Я не написал свой ответ по двум причинам: во-первых, я не знал, как это сделать, поэтому я нашел эту тему. Во-вторых, на этой странице есть исчерпывающий ответ Evk.
BlackOverlord

3
@DanW: Я знаю ответ; это первое предложение моего ответа: желаемая функция не разрешена в C #, потому что это плохая практика программирования . Как это сделать на C #? Вы этого не сделаете. Мысль о том, что я могу не знать ответа на этот вопрос, забавна, но мы оставим это без дальнейших комментариев. Теперь, если вы найдете этот ответ неудовлетворительным, почему бы не написать свой собственный ответ, который, по вашему мнению, работает лучше? Таким образом, мы все учимся на вашей мудрости и опыте, и вы также удвоите количество ответов, опубликованных в этом году.
Эрик Липперт

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

22

Вы не можете с C #. В IL это действительно поддерживается. Вы можете сделать вызов любого из ваших родительских классов без поддержки virt ... но, пожалуйста, не делайте этого. :)


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
Если у вас есть движок, который не может знать о базе или дочернем элементе, и говорит, чтобы он работал правильно при вызове этим движком, то Speak должен быть переопределением, а не новым. Если дочернему элементу требуется 99% базовых функций, но в случае с одним говорящим ему нужна функциональность супербазы ... это такая ситуация, о которой, как я понимаю, говорит OP, и в этом случае этот метод не будет работать. Это не редкость, и проблемы безопасности, которые привели к поведению C #, обычно не вызывают особого беспокойства.
Шаваис

Просто примечание в случае элементов управления и цепочек вызовов событий, методы часто защищены и, следовательно, недоступны в таком виде.
Shiv

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

Кажется, существует много этих вопросов, связанных с наследованием метода-члена от класса Grandparent, переопределением его во втором классе, а затем повторным вызовом его метода из класса Grandchild. Почему бы просто не унаследовать члены дедушки и бабушки внукам?

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"

Кажется простым .... здесь внук наследует метод дедушки и бабушки. Подумайте об этом ... вот как "Object" и его члены, такие как ToString (), наследуются всем классам в C #. Я думаю, что Microsoft не очень хорошо объяснила базовое наследование. Слишком много внимания уделяется полиморфизму и реализации. Когда я копаюсь в их документации, я не вижу примеров этой очень простой идеи. :(


-2

Если вы хотите получить доступ к данным базового класса, вы должны использовать ключевое слово this или использовать это ключевое слово как ссылку для класса.

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.