C# 显式地将派生类标记为基类的实现接口

C# 显式地将派生类标记为基类的实现接口,c#,inheritance,interface,C#,Inheritance,Interface,在这种情况下,输出将是“基本” 如果我显式地声明派生实现IBase(实际上已经由基类base实现,并且这样的注释似乎是无用的),那么输出将是“派生的” 这种行为的原因是什么 第15.3.5节,C#7,C#5规范第13.4.4节至第13.4.6节对此进行了解释。下面引用了相关部分,但基本上如果您明确声明一个类实现了一个接口,则会再次触发接口映射,因此编译器会将该类作为一个类,用于计算每个接口成员映射到哪个实现 13.4.4接口映射 类或结构必须提供类或结构的基类列表中列出的所有接口成员的实现。在实

在这种情况下,输出将是“基本”

如果我显式地声明派生实现IBase(实际上已经由基类base实现,并且这样的注释似乎是无用的),那么输出将是“派生的”

这种行为的原因是什么


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

13.4.4接口映射

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

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

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

13.4.5接口实现继承

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

class Derived : Base, IBase
{
    public Derived() => this.Name = "Derived";
    public new string Name { get; }
}
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();
文本框中的
Paint
方法隐藏了
Control
中的
Paint
方法,但它不会改变
控件的映射。Paint
IControl上。Paint
,通过类实例和接口实例调用
Paint
,将产生以下效果

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

13.4.6接口重新实现

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

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

class Derived : Base, IBase
{
    public Derived() => this.Name = "Derived";
    public new string Name { get; }
}
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();
Control
IControl.Paint
映射到
Control.IControl.Paint
的事实并不影响
MyControl
中的重新实现,后者将
IControl.Paint
映射到
MyControl.Paint


如果
Derived
未实现
IBase
并声明
新字符串名称
,则意味着
Derived.Name
IBase.Name
在逻辑上不相同。因此,当您访问
IBase.Name
时,它会在
Base
类中查找它,实现
IBase
。如果删除
new string Name
属性,则输出将是
Derived
,因为现在
Derived.Name
=
Base.Name
=
IBase.Name
。如果不恰当地实现了
IBase
,输出将是
派生的
,因为现在
派生的.Name
=
IBase.Name
。如果将
o
强制转换为
Derived
,则输出将是
Derived
,因为现在您正在访问
Derived.Name
,而不是
IBase.Name
,为什么它会有其他行为?你的期望是什么?期望-相同的输出,相同的成员访问。我不明白为什么在基类已经实现了这样的接口的情况下向类定义添加接口会改变事情。你知道
public new string Name{get;}
base
上的
Name
做了什么吗?嵌套类初始化mattersIn
IBase o=new-Derived()编译器有两种选择,它选择最佳匹配。你的介绍比引文更具解释性。但有一个问题,当你说再次触发接口映射的
时,
又是什么意思?接口是按什么顺序完成的,而不是从子接口到父接口,就像解释器的顺序一样?这意味着第一个接口映射只是“赢”了吗?感谢您提供这样的扩展答案。令人困惑的是,只需在接口到类定义中混入接口,就可以在两个类(基类、派生类)中的任何一个类中重新实现接口,而无需显式(语法)实现接口。@nl-x:我的意思是在编译
Base
时执行接口映射,然后在编译
派生类
时再次执行接口映射。是一个编译时选项,因此它没有执行时间顺序。。。我不知道你在问什么。我不应该提到(运行时)ex