5

考虑代码:

class ChildClass : BaseClass {

    public void Method1() {} //some other method

}

abstract class BaseClass : IChildInterface {

    public
    virtual //<- If we add virtual so that this method can be overridden by ChildClass, we get StackOverflowException and DoWork() implementation in IChildInterface is never called.
    void DoWork() {
     //base class specific implmentation
     ((IChildInterface)this).DoWork(); //call into default implementation provided by IChildInterface
    }

}

interface IChildInterface : IBaseInterface {

    void IBaseInterface.DoWork() {
     //implmentation
    }

}

interface IBaseInterface {

    void DoWork();

}

问题是,如果我们标记DoWork()BaseClass以便virtual它可以被子类覆盖,它会阻止它调用IChildInterface的默认实现DoWork(),从而导致StackOverflowException.

如果我们从中删除virtual修饰符,一切正常,并调用的默认实现。DoWork()BaseClassIChildInterfaceDoWork()

这种行为是错误还是设计使然?

有没有办法让一些子类提供他们自己的实现DoWork()(从而覆盖BaseClass的实现)但仍然能够使用 IChildInterface的默认实现DoWork()

4

3 回答 3

4

您正在BaseClass.DoWork递归调用,如果幸运的话,将导致 StackOverflowException。如果调用是方法中的最后一个调用,由于尾调用优化,您将获得无限递归。您最终会导致核心卡在 100%,直到您终止应用程序。

这段代码:

public virtual void DoWork() {
   ((IChildInterface)this).DoWork(); by IChildInterface
}

等同于:

//That's the actual implementation of the interface method
public virtual void DoWork() {
     DoWork(); 
}

virtual关键字无关紧要。没有它你仍然会得到无限递归。不管它是否存在,这一行都会在一段时间后抛出 StackOverflowException :

new ChildClass().DoWork();

当您实现时BaseClass.DoWork,它成为每个人都可以使用的单一实现,除非被子类覆盖。

接口不是抽象类,即使在 C# 8 中也是如此。默认方法实现不是实际方法。顾名思义,这是一个默认实现。当没有更好的实现可用时使用它。当方法已经在类中实现时,您不能调用默认实现。

事实上,几乎在所有情况下,您都不会期望调用默认方法。通过接口显式调用 DIM,与使用显式接口实现的方式相同。该方法的调用者希望运​​行最派生的实现,而不是基本或中级实现。

此外,即使在以前的 C# 版本中,您也不希望强制转换为接口来更改实际调用的方法。你会期望只有类。要调用基类实现,您将使用base关键字。BaseClass虽然的基类是Object没有DoWork方法的。

如果您使用:

void DoWork() {
    base.DoWork(); 
}

你会得到一个CS0117: 'object' does not contain a definition for 'DoWork'

更新

C# 设计团队已经对此有所了解。如果没有运行时支持,这将无法有效实施,并于 2019 年 5 月被削减。运行时优化使 DIM 调用与其他调用一样便宜,无需装箱等。

建议的语法base(IMyInterface)call :

interface I1
{ 
    void M(int) { }
}

interface I2
{
    void M(short) { }
}

interface I3
{
    override void I1.M(int) { }
}

interface I4 : I3
{
    void M2()
    {
        base(I3).M(0) // What does this do?
    }
}
于 2019-12-18T08:13:29.787 回答
2

由于默认情况下接口内的所有方法都是虚拟的,因此 DoWork 在您提供的每个定义/实现中都是虚拟的,但 ChildClass 除外。当您显式使用 IChildInterface 的 DoWork 时,它隐式使用 BaseClass.DoWork ,然后((IChildInterface)this).DoWork();再次显式使用。等等。你有这个永无止境的循环,因此你得到了 StackOverflow。

于 2019-12-18T08:09:47.190 回答
0

为了以后的读者...

virtual虽然@Panagiotis 提供的公认答案是正确的,因为修饰符是否存在并且无论如何都会发生没有区别StackOverflowExcpetion,但我想为我解决的问题提供一个具体的答案。

DoWork()在类中实现而不是在类中实现的全部目的IChildInterface是为了代码重用并保持“DRY”。然而,实现的类IChildInterface应该能够在IChildInterface.

其中存在一个问题,因为((IChildInterface)this).DoWork();任何实现的类(抽象或非抽象)调用IChildInterface将导致无限递归。唯一合理的出路似乎是使用protected static成员(实际上是在Microsoft Docs中建议的):

class ChildClass : BaseClass {

    public void Method1() {} //some other method

}

abstract class BaseClass : IChildInterface {

    public virtual void DoWork() {
     // Base class specific implementation here

     // Then call into default implementation provided by IChildInterface:
     // Instead of this: ((IChildInterface)this).DoWork();
     // Use static implementation:
     IChildInterface.DoWork(this);
    }

}

interface IChildInterface : IBaseInterface {

    protected static void DoWork(IChildInterface it){
      // Implementation that works on instance 'it'
    }
    void IBaseInterface.DoWork() => IChildInterface.DoWork(this);

}

interface IBaseInterface {

    void DoWork();

}

在上述解决方案中,我们通过仍然拥有一个(核心)实现来保持“DRY” DoWork(),但它位于protected static接口的成员中,IChildInterface而不是其继承层次结构的一部分。

然后,就继承层次结构而言,派生自/实现的所有接口/类IChildInterface都可以简单地用于IChildInterface.DoWork(this)访问默认实现。这适用于IChildInterface自身。

于 2019-12-18T15:36:42.773 回答