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

Fit*_*Dev 5 .net c# c#-8.0 .net-core-3.0 default-interface-member

考虑代码:

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

}
Run Code Online (Sandbox Code Playgroud)

问题是,如果我们庆祝DoWork()BaseClassvirtual,这样它可以通过子类覆盖,防止它从调入IChildInterface的缺省实现DoWork(),引起StackOverflowException

如果我们virtualDoWork()中删除修饰符BaseClass,则一切正常,并且调用IChildInterface的默认实现。DoWork()

这种行为是错误还是有意为之?

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

Pan*_*vos 4

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

这段代码:

public virtual void DoWork() {
   ((IChildInterface)this).DoWork(); by IChildInterface
}
Run Code Online (Sandbox Code Playgroud)

等同于:

//That's the actual implementation of the interface method
public virtual void DoWork() {
     DoWork(); 
}
Run Code Online (Sandbox Code Playgroud)

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

new ChildClass().DoWork();
Run Code Online (Sandbox Code Playgroud)

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

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

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

此外,即使在以前的 C# 版本中,您也不会期望通过转换为接口来更改实际调用的方法。你只希望在课堂上能做到这一点。要调用基类实现,您可以使用关键字baseBaseClasswhile的基类Object没有方法DoWork

如果您使用:

void DoWork() {
    base.DoWork(); 
}
Run Code Online (Sandbox Code Playgroud)

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

更新

C# 设计团队已经考虑过这一点。如果没有运行时支持,这无法有效实现,并于 2019 年 5 月被削减。运行时优化使得 DIM 调用与其他调用一样便宜,无需装箱等。

建议的语法是调用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?
    }
}
Run Code Online (Sandbox Code Playgroud)