使成员成为虚拟成员会阻止调用默认接口实现并导致 C# 8 中的 StackOverflowException

Making member virtual prevents calling default interface implementation and causes StackOverflowException in C# 8

考虑代码:

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

}

问题是,如果我们将 BaseClass 中的 DoWork() 标记为 virtual 以便它可以被 child 类 覆盖,它会阻止它从调用 IChildInterfaceDoWork() 默认实现,导致 WhosebugException.

如果我们从 BaseClass 中的 DoWork() 中删除 virtual 修饰符,一切正常,并且 IChildInterface 默认实现 DoWork() 被称为.

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

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

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

您正在递归调用 BaseClass.DoWork,如果幸运的话,这将导致 WhosebugException。如果调用是方法中的最后一个调用,由于尾调用优化,您将获得无限递归。在您终止应用程序之前,您最终会遇到核心卡在 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关键字无关紧要。没有它,你仍然会得到无限递归。无论是否存在,此行都会在一段时间后抛出 WhosebugException :

new ChildClass().DoWork();

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

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

事实上,在几乎所有情况下,您不会期望调用默认方法。通过接口显式调用 DIM,这与使用显式接口实现的方式相同。该方法的调用者期望 most-derived 实现为 运行,而不是基础或 mid-level 实现。

此外,即使在以前的 C# 版本中,您也不会期望转换为接口来更改实际调用的方法。您会期望只有 classes。要调用基础 class 实现,您需要使用 base 关键字。 BaseClass 的基础 class 是 Object,它没有 DoWork 方法。

如果您使用过:

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

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

更新

C#设计团队已经考虑到这一点。如果没有 运行 时间支持和 was cut i May 2019,这将无法有效实施。运行时优化使 DIM 调用与其他调用一样便宜,无需装箱等。

proposed syntax 是一个 base(IMyInterface) 调用:

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?
    }
}

为了未来的读者...

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

与 class 相比,在 IChildInterface 中实现 DoWork() 的全部意义在于代码重用和保留 "DRY"。然而,实现 IChildInterface 的 类 应该能够在 IChildInterface.

提供的功能之上添加自己的功能

这就是一个问题,因为从实现 IChildInterfaceany class(抽象与否)调用 ((IChildInterface)this).DoWork(); 将导致无限递归。唯一合理的出路似乎是使用 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();

}

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

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