如何调用base.base.method()方法?

157
// 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中调用。 /*这不是预期的*/
从Base中调用。

如何重写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.");
    }
}

编辑以匹配您的更新。 - JoshJordan
它的工作方式符合预期,也应该是这样的。而且,你的标题有误导性。你真正想问的是“如何避免调用base.base.method,同时仍然调用base.base.base.method?” - Jason Cheng
13个回答

134

仅想在这里添加一点内容,因为即使经过多次后人们仍然会回到这个问题。当然这是不好的实践方式,但从原理上讲仍然有可能做到作者想要的:

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

71
我很喜欢这个答案,因为问题并不是询问是否建议这样做或者是否是一个好主意,而是在问是否有一种方法来做到,并且如果有,那么是什么方法。 - Shavais
7
当你必须处理缺乏可扩展性的框架/库时,这种方法特别有用。例如,我从未需要为高度可扩展的NHibernate使用这种解决方案。但是,为了处理Asp.Net Identity、Entity Framework和Asp.Net Mvc,我经常使用这种技巧来处理它们缺少的功能或不适合我的需求的硬编码行为。 - Frédéric
4
谢谢! 我想提醒其他人,在回答问题时停止引用指南。这个问题是有原因的,不是在被教训。如果你不知道,那就不要回答! 我也想鼓励大家抨击代码警察,这样或许会阻止他们发布无关紧要的回答。 如果在回答问题后,你觉得有必要引用指南,则可以继续提及。 - DanW
1
如果我们需要底层函数的返回值怎么办? - Perkins
3
没关系,我已经明白了。将类型转换为Func<stuff>而不是Action - Perkins
显示剩余4条评论

102

这是一种不良的编程实践,在C#中不被允许。这是不良编程实践,因为:

  • grandbase(指直接基类)的细节是基类的实现细节;你不应该依赖它们。基类提供了 grandbase 的抽象层;你应该使用该抽象层,而不是构建一个绕过它的方法。

  • 为了说明上一点的具体例子:如果允许这种模式,那么它将成为使代码容易受到脆弱基类故障的另一种方式。假设 C 从 B 派生,B 从 A 派生。C 中的代码使用 base.base 调用 A 类的方法。然后,B 的作者意识到他们在类 B 中放置了太多的内容,更好的方法是制作一个派生自 A 的中间类 B2,并且 B 派生自 B2。在这个变化之后,C 中的代码调用的是 B2 中的方法,而不是 A 中的方法,因为 C 的作者假设 B 的实现细节,即其直接基类是 A,永远不会改变。C# 中的许多设计决策都是为了减少各种类型的脆弱基础失败的可能性;完全禁止使用 base.base 可以避免这种特定类型的故障模式。

  • 你从你的基类中派生,是因为你喜欢它所做的事情,并想要重用和扩展它。如果你不喜欢它的工作方式,并想要绕过它而不是与它一起工作,那么你为什么要从它派生呢?如果想要使用和扩展 grandbase 的功能,则应该从 grandbase 自己派生。

  • 基类可能需要某些不变量,以维护安全性或语义一致性,这些不变量由基类如何使用 grandbase 的方法维护。允许基类的派生类跳过维护这些不变量的代码可能会使基类处于不一致、损坏的状态。


4
如果您希望,请优先使用组合而不是继承。您的类可以取Base或GrandBase的实例,然后将功能委托给该实例。 - Eric Lippert
4
既然您对这个话题感到强烈,为什么不撰写自己的答案呢?这样我们所有人都可以从您对这个主题的智慧中受益,同时您也会在StackOverflow上编写两个答案,将您的总贡献加倍。这是一种双赢的局面。 - Eric Lippert
6
@Eric Lippert:我没有写自己的答案有两个原因:第一,我不知道该怎么做,所以我找到了这个主题。第二,在这个页面下面有一个全面的答案由Evk提供。 - BlackOverlord
4
@DanW: 我知道答案,它在我的回答的第一句话中: C#不允许所需的功能,因为这是一种不好的编程实践。如何在C#中实现这个呢?你无法这样做。你认为我可能不知道这个问题的答案是很有趣的,但我们将不再发表进一步的评论。如果您觉得这个答案不满意,为什么不写下您自己认为更好的答案?这样我们都可以从您的智慧和经验中学习,而您还可以使您今年发布的答案数量翻倍。 - Eric Lippert
18
如果基础代码存在缺陷需要覆盖,比如第三方控件,而实现中包含对祖先代码的调用,该怎么办?在真实的应用中,这可能是至关重要的,并且需要进行热修复,因此等待第三方供应商可能不是一个选择。这是坏习惯与生产环境现实之间的冲突。 - Shiv
显示剩余8条评论

24

从C#本身是无法实现的。但是从IL(Intermediate Language,一种中间语言)来看,这个确实是被支持的。你可以调用非虚方法到任何一个父类……但请不要这样做。 :)


10

为什么不直接将子类强制转换为特定的父类,然后调用特定的实现呢?这是一个特殊情况,需要使用特殊解决方法。尽管在子类方法中需要使用 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时需要正常工作,则speak需要是一个override而不是new。如果子类需要99%的基类功能,但在一个speak情况下,它需要超级基类的功能...这就是我理解OP所谈论的情况,如果是这种情况,那么这种方法将行不通。这并不罕见,引起C#行为的安全问题通常并不是非常令人担忧。 - Shavais
在控件和事件调用链的情况下,需要注意的是,这些方法通常是受保护的,因此不能像这样访问。 - Shiv
3
这并不使用继承,你最好为每个 Speak 给一个完全独特的名称。 - Nick Sotiros
是的,它并不是完全继承,而是使用了父类的接口。 - Kruczkowski Piotr

10

答案是(我知道这不是你想要的):

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

5
您也可以在一级派生类中创建一个简单的函数,以调用爷爷级别的基础函数。

1
确切地说,这保留了每个人都担心的整个抽象方案,并突显了抽象方案有时会带来更多麻烦的方式。 - Shavais

3

我的建议是,在工具包类中实现所需的功能,并从需要调用该功能的任何地方调用它:

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

这需要考虑访问权限,您可能需要添加一些内部访问器方法来促进功能实现。


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

似乎有很多关于从祖父类继承成员方法,在第二个类中覆盖它,然后再从孙子类中调用其方法的问题。为什么不直接将祖先的成员继承到孙子类中呢?

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#中向所有类继承的方式。我认为微软没有很好地解释基本的继承概念。太过于关注多态性和实现。当我查阅他们的文档时,没有任何关于这个非常基本的概念的例子。 :(


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接