提问人:Bartosz 提问时间:10/10/2020 最后编辑:Bartosz 更新时间:10/13/2020 访问量:730
C# 8.0 默认接口成员的意外行为
Unexpected behavior of a C# 8.0 default interface member
问:
请考虑以下代码:
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
类没有 成员的隐式或显式实现,因此我希望默认实现用于 ,因为继承了 的接口映射并且不会显式重新实现。根据 ECMA-334 (18.6.6) 和 C# 6.0 语言规范:A
I
C
C
A
I
类继承其基类提供的所有接口实现。
如果不显式重新实现接口,派生类就不能以任何方式更改它从其基类继承的接口映射。
特别是,我期望以下输出:
I.M1
I.M2
这确实是当未被声明为抽象时发生的情况。A
上述代码的行为是在 C# 8.0 中预期的,还是某些错误的结果?如果有意的话,为什么方法只有在声明为虚拟时才隐式实现相应的成员(在的情况下但不是),并且只有在声明为抽象时才隐式实现?C
I
M2
M1
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
Program.C
Program.C.Method
Program
Program
Program.C.Method
Library.I
Library.A.OtherMethod
Program.C.Method
这看起来像是一种脆性基类问题。被宣布为公开的事实应该是无关紧要的。请参阅 Eric Lippert 的这篇博文,其中描述了一个不同但有些相似的场景。Program.C.Method
答:
自 C# 8.0 引入以来,支持接口的默认实现。通过此介绍,接口的实现成员的查找过程已更改。关键部分是关于如何定义实例(在您的示例 obj 中)或类型语法。
让我们从 7.3 进行成员解析的方法开始,并替换为 运行此操作时,将打印以下输出:I obj = new C();
C obj = new C();
C.M1 C.M2
正如你所看到的,WriteLine 将结果打印为类 C 定义的实现。这是因为类型语法指的是一个类,而“第一行”实现是类 C 的实现。
现在,当我们将其改回时,我们会看到不同的结果,即: 这是因为虚拟和抽象成员不会像 M1(未标记为虚拟)那样被最派生的实现替换。I obj = new C();
I.M1 C.M2
现在主要问题仍然存在,为什么 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
评论