将派生类明确标记为实现基类的接口

时间:2017-10-03 09:34:26

标签: c# inheritance interface

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

2 个答案:

答案 0 :(得分:18)

在C#5规范的第13.4.4至13.4.6节中对此进行了解释。下面引用了相关的部分,但基本上如果你明确声明一个类实现了一个接口,那么再次触发接口映射,所以编译器将那个类作为用来计算每个实现的实现接口成员映射到。

  

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() {...}
}
     

Paint TextBox方法隐藏了Paint中的Control方法,,但它不会改变Control.Paint到{{}的映射1}} ,并通过类实例和接口实例调用IControl.Paint将产生以下影响

Paint
     

...

     

13.4.6界面重新实现

     

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

     

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

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();
     

interface IControl { void Paint(); } class Control: IControl { void IControl.Paint() {...} } class MyControl: Control, IControl { public void Paint() {} } Control映射到IControl.Paint并不会影响Control.IControl.Paint中重新实施的事实,MyControlIControl.Paint映射到MyControl.Paint }。

答案 1 :(得分:1)

如果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