使成员成为虚拟成员会阻止调用默认接口实现并导致 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 类 覆盖,它会阻止它从调用 IChildInterface
的 DoWork()
默认实现,导致 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
.
提供的功能之上添加自己的功能
这就是一个问题,因为从实现 IChildInterface
的 any 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
本身。
考虑代码:
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 类 覆盖,它会阻止它从调用 IChildInterface
的 DoWork()
默认实现,导致 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
.
这就是一个问题,因为从实现 IChildInterface
的 any 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
本身。