Подтвердить что ты не робот

Явная маркировка производного класса как интерфейса реализации базового класса

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, С# 7

4b9b3361

Ответ 1

Объясняется в разделах 13.4.4-13.4.6 спецификации С# 5. Соответствующие разделы приводятся ниже, но в основном, если вы явно заявляете, что класс реализует интерфейс, который снова запускает сопоставление интерфейсов, поэтому компилятор принимает этот класс как тот, который будет использоваться для разработки, к какой реализации сопоставляется каждый член интерфейса.

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 Наследование реализации интерфейса

Класс наследует все реализации интерфейса, предоставляемые его базовыми классами. Без явного повторного внедрения интерфейса производный класс никоим образом не может изменять сопоставления интерфейсов, которые он наследует от своих базовых классов. Например, в объявлениях

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

метод Paint в TextBox скрывает метод Paint в Control, , но он не изменяет отображение 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 Реализация интерфейса

Класс, который наследует реализацию интерфейса, разрешается повторно реализовать интерфейс, включив его в список базового класса.

Повторная реализация интерфейса следует точно таким же правилам сопоставления интерфейсов, что и первоначальная реализация интерфейса. Таким образом, унаследованное сопоставление интерфейсов не оказывает никакого влияния на отображение интерфейса, установленное для повторной реализации интерфейса. Например, в объявлениях

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

тот факт, что Control отображает IControl.Paint на Control.IControl.Paint, не влияет на повторную реализацию в MyControl, которая отображает IControl.Paint на MyControl.Paint.

Ответ 2

Если 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.