问题描述
考虑以下代码:
interface I {
string M1() => "I.M1";
string M2() => "I.M2";
}
abstract class A : I {}
class C : A {
public string M1() => "C.M1";
public virtual string M2() => "C.M2";
}
class Program {
static void Main() {
I obj = new C();
System.Console.WriteLine(obj.M1());
System.Console.WriteLine(obj.M2());
}
}
它在.NET Core 3.1.402中产生以下意外输出:
I.M1
C.M2
类A
没有I
成员的隐式或显式实现,所以我希望默认实现用于C
,因为C
继承了A
的接口映射,并且没有显式重新实现I
。根据ECMA-334(18.6.6)和C#6.0语言规范:
一个类继承其基类提供的所有接口实现。
没有显式重新实现接口,派生类就无法以任何方式更改其从其基类继承的接口映射。
特别是,我希望得到以下输出:
I.M1
I.M2
当A
未声明为抽象时,确实会发生这种情况。
以上代码的行为是C#8.0中预期的,还是某些错误的结果?如果需要,为什么C
中的方法仅在声明为虚拟时(对于I
而不是M2
)并且仅当声明为虚拟时才隐式实现M1
的相应成员{1}}被声明为抽象吗?
编辑:
虽然我仍然不清楚这是错误还是功能(我倾向于认为这是错误,并且到目前为止,第一条评论中的讨论尚无定论),但我提出了一个更加危险的选择情况:
A
请注意,接口class Library {
private interface I {
string Method() => "Library.I.Method";
}
public abstract class A: I {
public string OtherMethod() => ((I)this).Method();
}
}
class Program {
private class C: Library.A {
public virtual string Method() => "Program.C.Method";
}
static void Main() {
C obj = new C();
System.Console.WriteLine(obj.OtherMethod());
}
}
和类Library.I
对于各自的类是 private 。特别是,方法Program.C
应该从类Program.C.Method
外部不可访问。类Program
的作者可能认为完全控制何时调用方法Program
,甚至可能不知道接口Program.C.Method
(因为它是私有的)。但是,它从Library.I
中被调用,因为输出为:
Library.A.OtherMethod
这看起来像是一种脆性的基类问题。 Program.C.Method
被宣布为公开的事实应该是无关紧要的。参见埃里克·利珀特(Eric Lippert)的this blog post,其中描述了一种不同但有些相似的情况。
解决方法
自从C#8.0引入以来,支持接口的默认实现。通过此介绍,已更改了接口的实现成员的查找过程。关键部分在于如何定义实例(在您的示例obj中)或类型语法。
让我们从进行成员解析的7.3种方法开始,并将I obj = new C();
替换为C obj = new C();
。运行此命令时,将输出以下输出:
C.M1 C.M2
您可以看到两个WriteLine都将结果打印为类C定义的实现。这是因为类型语法引用了一个类,而“ first in line”实现是类C的实现。
现在,当我们将其更改回I obj = new C();
时,会看到不同的结果,即:
I.M1 C.M2
这是因为虚拟成员和抽象成员不会像M1(未标记为虚拟)那样被大多数派生的实现所取代。
现在主要问题仍然存在,为什么C中的方法仅在声明为虚时(对于M2而不是M1时)并且仅在将A声明为抽象时才隐式实现I的相应成员?
当类A是一个非抽象类时,它是“主动”实现接口,而当它是抽象类时,该类只需要在其中插入抽象类的类也实现接口。 当我们查看您的示例时,我们不能这样写:|
A obj = new C();
System.Console.WriteLine(obj.M1()); // Method M1() is not defined
有关更多信息,请点击此处:https://github.com/dotnet/roslyn/blob/master/docs/features/DefaultInterfaceImplementation.md
以下是其结果的一些变化:
I obj = new C(); // with A as abstract class
结果是
I.M1 C.M2
I obj = new C(); // with A as class
结果是
I.M1 I.M2
C obj = new C(); // with or without A as abstract class
结果是
C.M1 C.M2
I obj = new A(); // with A as class
结果是
I.M1 I.M2