【问题标题】:Unexpected behavior of a C# 8.0 default interface memberC# 8.0 默认接口成员的意外行为
【发布时间】:2020-10-10 15:42:55
【问题描述】:

考虑以下代码:

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 的情况下)并且仅在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.IProgram.C 类对于各自的类是私有的。特别是,方法Program.C.Method 应该不能从类Program 外部访问。 Program 类的作者可能认为可以完全控制何时调用方法 Program.C.Method,甚至可能不知道接口 Library.I(因为它是私有的)。但是,它是从Library.A.OtherMethod 调用的,因为输出是:

Program.C.Method

这看起来像是一种脆弱的基类问题。 Program.C.Method 被声明为 public 的事实应该是无关紧要的。请参阅 Eric Lippert 的 this blog post,它描述了一个不同但有些相似的场景。

【问题讨论】:

  • @aepot 感谢您进行更多调查并提交适当的错误报告。尽管我不了解运行时的内部结构,但我确实倾向于认为这是一个错误——非常严重。我在帖子中添加了一个新场景来解释我的观点。

标签: c# c#-8.0 interface-implementation default-interface-member


【解决方案1】:

自 C# 8.0 引入以来,支持接口的默认实现。通过此介绍,实现成员的查找过程已针对接口进行了更改。关键部分是关于如何定义实例(在您的示例 obj 中)或类型语法。

让我们从 7.3 的成员解析方式开始,将 I obj = new C(); 替换为 C obj = new C(); 运行时将打印以下输出: C.M1 C.M2

您可以看到,WriteLine 都将结果打印为 C 类定义的实现。这是因为类型语法引用了一个类,而“第一行”实现是 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-22
    • 2010-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-24
    相关资源
    最近更新 更多