【问题标题】:Explicitly marking derived class as implementing interface of base class将派生类显式标记为基类的实现接口
【发布时间】:2023-03-12 12:25:02
【问题描述】:
interface IBase
{
    string Name { get; }
}

class Base : IBase
{
    public Base() => this.Name = "Base";
    public string Name { get; }
}

class Derived : Base//, IBase
{
    public Derived() => this.Name = "Derived";
    public new string Name { get; }
}


class Program
{
    static void Main(string[] args)
    {
        IBase o = new Derived();
        Console.WriteLine(o.Name);
    }
}

在这种情况下,输出将是“Base”。

如果我明确声明 Derived 实现了 IBase(实际上它已经由基类 Base 实现,并且这样的注释似乎没用)输出将是“Derived”

class Derived : Base, IBase
{
    public Derived() => this.Name = "Derived";
    public new string Name { get; }
}

这种行为的原因是什么?

VS 15.3.5,C# 7

【问题讨论】:

  • 为什么它应该表现得不一样?你的期望是什么?
  • 期望 - 相同的输出,相同的成员访问。我不明白为什么当基类已经实现的接口可以改变事情时,为什么将接口添加到类定义中。
  • 你明白public new string Name { get; }Base 上对Name 做什么吗?
  • 嵌套类初始化很重要
  • IBase o = new Derived(); 中编译器有2个选择,它选择最佳匹配。

标签: c# inheritance interface


【解决方案1】:

C# 5 规范的第 13.4.4 到 13.4.6 节对此进行了解释。下面引用了相关部分,但基本上,如果您明确声明一个类实现了一个接口,则会再次触发接口映射,因此编译器将 that 类作为用于确定每个类的实现接口成员被映射到。

13.4.4 接口映射

一个类或结构必须提供该类或结构的基类列表中列出的所有接口成员的实现。在实现类或结构中定位接口成员的实现的过程称为接口映射。

类或结构C 的接口映射为C 的基类列表中指定的每个接口的每个成员定位一个实现。特定接口成员I.M 的实现,其中I 是声明成员M 的接口,是通过检查每个类或结构S 来确定的,从C 开始并重复每个连续的C 的基类,直到找到匹配项:

  • 如果S 包含与IM 匹配的显式接口成员实现的声明,则此成员是I.M 的实现。
  • 否则,如果S 包含与M 匹配的非静态公共成员的声明,则该成员是I.M 的实现。如果多个成员匹配,则未指定哪个成员是I.M 的实现。仅当S 是构造类型,其中在泛型类型中声明的两个成员具有不同的签名,但类型参数使它们的签名相同时,才会发生这种情况。

...

13.4.5 接口实现继承

类继承其基类提供的所有接口实现。 如果不显式地重新实现接口,派生类就不能以任何方式更改它从其基类继承的接口映射。例如,在声明中

interface IControl
{
    void Paint();
}
class Control: IControl
{
    public void Paint() {...}
}
class TextBox: Control
{
    new public void Paint() {...}
}

TextBox 中的Paint 方法隐藏了Control 中的Paint 方法,但它不会改变Control.PaintIControl.Paint 的映射,并调用@ 987654344@通过类实例和接口实例会有如下效果

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();            // invokes Control.Paint();
t.Paint();            // invokes TextBox.Paint();
ic.Paint();           // invokes Control.Paint();
it.Paint();           // invokes Control.Paint();

...

13.4.6 接口重新实现

允许继承接口实现的类通过将其包含在基类列表中来重新实现接口。

接口的重新实现遵循与接口的初始实现完全相同的接口映射规则。因此,继承的接口映射对为重新实现接口而建立的接口映射没有任何影响。例如,在声明中

interface IControl
{
    void Paint();
}
class Control: IControl
{
    void IControl.Paint() {...}
}
class MyControl: Control, IControl
{
    public void Paint() {}
}

ControlIControl.Paint 映射到Control.IControl.Paint 的事实不影响MyControl 中的重新实现,后者将IControl.Paint 映射到MyControl.Paint

【讨论】:

  • 您的介绍比引文更具解释性。但是有个问题,当你说that triggers interface mapping again时,again是什么意思?接口是按照什么顺序完成的,而不是从子级到父级,就像构造函数被触发的顺序一样?意味着第一个接口映射只是“获胜”?
  • 感谢您提供如此扩展的答案。令人困惑的一点是,可以重新实现接口,而无需在任何两个类(Base、Derived)中显式(语法)实现接口,只需通过在类定义的接口中混入。
  • @nl-x:我的意思是在编译Base时进行接口映射,然后在编译Derived时再次进行。这是一个编译时的选择,所以它没有执行时的顺序......我不确定你在问什么。
  • 我不应该提到构造函数的(运行时)执行顺序,这令人困惑。但如果我理解正确,接口成员映射到父类的成员和子类的成员。映射是否被覆盖,或者两个映射都存在,并且子映射在调用时“获胜”?
  • @nl-x:它没有同时映射到两者 - 映射是从头开始为 Derived 重新完成的,因为它明确表示它实现了接口。你可以认为每个类都有它实现的所有接口的完整映射,包括继承的接口;如果类没有声明它实现了一个接口,则映射继承自基类。
【解决方案2】:

如果Derived没有实现IBase并声明了new string Name,这意味着Derived.NameIBase.Name在逻辑上是不一样的。所以当你访问IBase.Name时,它会在Base类中寻找它,实现IBase。如果删除 new string Name 属性,则输出将为 Derived,因为现在 Derived.Name = Base.Name = IBase.Name。如果你明确地实现IBase,输出将是Derived,因为现在Derived.Name = IBase.Name。如果将o 转换为Derived,则输出将为Derived,因为现在您访问的是Derived.Name 而不是IBase.Name

【讨论】:

    猜你喜欢
    • 2013-09-30
    • 2010-09-22
    • 2012-03-14
    • 1970-01-01
    • 2017-07-18
    • 1970-01-01
    • 1970-01-01
    • 2014-06-17
    相关资源
    最近更新 更多