将派生类明确标记为实现基类的接口
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
它在C#5规范的第13.4.4至13.4.6节中进行了解释。 下面引用了相关的部分,但基本上如果你明确声明一个类实现了一个接口,那么它会再次触发接口映射,因此编译器将该类作为用于确定每个接口成员映射到哪个实现的类。
13.4.4接口映射
类或结构必须提供类或结构的基类列表中列出的接口的所有成员的实现。 在实现类或结构中定位接口成员的实现的过程称为接口映射。
类或结构
C
接口映射定位C
的基类列表中指定的每个接口的每个成员的实现。 特定接口成员IM
,其中I
是声明成员M
的接口,通过检查每个类或结构S
来确定,从C
开始并重复每个连续的C
类基类,直到找到匹配为止:
- 如果
S
包含与I
和M
匹配的显式接口成员实现的声明,则此成员是IM
的实现。- 否则,如果
S
包含与M匹配的非静态公共成员的声明,则此成员是IM
的实现。 如果多个成员匹配,则未指定哪个成员是IM
的实现。 只有当S
是一个构造类型时,才会出现这种情况,其中generics类型中声明的两个成员具有不同的签名,但类型参数使它们的签名相同。…
13.4.5接口实现inheritance
类inheritance其基类提供的所有接口实现。 在没有显式重新实现接口的情况下,派生类不能以任何方式改变它从其基类inheritance的接口映射。 例如,在声明中
interface IControl { void Paint(); } class Control: IControl { public void Paint() {...} } class TextBox: Control { new public void Paint() {...} }
TextBox
的Paint
方法隐藏了Control
的Paint
方法, 但它不会改变Control.Paint
到IControl.Paint
的映射 ,并且通过类实例和接口实例调用Paint
将具有以下效果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接口重新实现
允许inheritance接口实现的类通过将其包含在基类列表中来重新实现接口。
接口的重新实现遵循与接口的初始实现完全相同的接口映射规则。 因此,inheritance的接口映射对于为接口的重新实现而建立的接口映射没有任何影响。 例如,在声明中
interface IControl { void Paint(); } class Control: IControl { void IControl.Paint() {...} } class MyControl: Control, IControl { public void Paint() {} }
Control
将IControl.Paint
映射到Control.IControl.Paint
事实不影响MyControl
的重新实现,MyControl
将IControl.Paint
映射到MyControl.Paint
。
如果Derived
没有实现IBase
并声明new string Name
,那意味着Derived.Name
和IBase.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
。