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

Почему этот код С# возвращает то, что он делает

Может кто-то, пожалуйста, помогите мне понять, почему этот фрагмент кода возвращает "Bar-Bar-Quux"? Мне трудно понять это даже после чтения на интерфейсах.

interface IFoo
{ 
    string GetName();
}

class Bar : IFoo
{
    public string GetName() { return "Bar"; }
}

class Baz : Bar
{
    public new string GetName() { return "Baz"; }
}

class Quux : Bar, IFoo
{
    public new string GetName() { return "Quux"; }
}

class Program
{
    static void Main()
    {
        Bar f1 = new Baz();
        IFoo f2 = new Baz();
        IFoo f3 = new Quux();
        Console.WriteLine(f1.GetName() + "-" + f2.GetName() + "-" + f3.GetName());
    }
}
4b9b3361

Ответ 1

Здесь есть две вещи. Один из них скрывается. Это довольно хорошо известно и охватывает в других местах. Другая, менее известная особенность - повторная реализация интерфейса, описанная в разделе 13.4.6 спецификации С# 5. Цитировать:

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

и

Унаследованные публичные объявления участников и декларации с явным именем участника интерфейса участвуют в процессе сопоставления интерфейса для повторно реализованных интерфейсов.

Результатом для f1.GetName() является "Bar", потому что метод Baz.GetName скрывается Bar.GetName, а f1 объявлен как тип Bar. Отправки к реализации типа выполнения не существует, если она явно не объявлена ​​как виртуальная и переопределенная.

Аналогично, для f2.GetName(), Baz.GetName скрывает реализацию в Bar, поэтому она не вызывается при использовании отправки через ссылку на интерфейс. Интерфейс "сопоставляется" с методом, объявленным в Bar, потому что это тип, на котором был объявлен интерфейс. Не имеет значения, что Baz имеет совместимый метод с тем же именем. Правила сопоставления интерфейсов определены в разделе 13.4.4 спецификации. Если GetName было объявлено виртуальным в Bar, оно может быть переопределено, которое затем будет вызвано через интерфейс. Поэтому результатом является также "Бар".

Для f3.GetName(), Quux повторно реализует IFoo, поэтому он может определить собственное сопоставление с GetName. Обратите внимание, что он также скрывает реализацию, унаследованную от Bar. Нет необходимости использовать новую для повторной реализации, она просто подавляет предупреждение о скрытии. Поэтому результатом является "Quux".

Итак, это объясняет вывод, который вы видите: "Bar-Bar-Quux"

Этот пост Эрика Липперта обсуждает еще несколько нюансов в этой сложной функции.

Ответ 2

Интерфейсы по определению не имеют связанной реализации, т.е. их методы всегда являются виртуальными и абстрактными. Напротив, класс Bar выше определяет конкретную реализацию для GetName. Это удовлетворяет контракту, требуемому для реализации IFoo.

Класс Baz теперь наследуется от Bar и объявляет метод new GetName. То есть родительский класс Bar имеет метод с тем же именем, но он полностью игнорируется при явной работе с объектами Baz.

Однако, если объект Baz задан как Bar или просто назначен переменной типа Bar или IFoo, он будет делать так, как он сказал, и вести себя как Bar. Другими словами, имя метода GetName относится к Bar.GetName вместо Baz.GetName.

Теперь, в третьем случае, Quux наследует от Bar и реализует IFoo. Теперь, когда он представлен как IFoo, он предоставит свою собственную реализацию (согласно спецификации, приведенной в ответе Майка Z).

Когда Quux используется как панель, он возвращает "Bar", как это делает Баз.

Ответ 3

Вывод Bar-Bar-Quux в результате 3 вызовов GetName() в вызове метода Console.WriteLine.

Bar f1 = new Baz();
IFoo f2 = new Baz();
IFoo f3 = new Quux();
Console.WriteLine(f1.GetName() + "-" + f2.GetName() + "-" + f3.GetName());
//Bar-Bar-Quux

Давайте рассмотрим каждый вызов, чтобы было более ясно, что происходит.

f1.GetName()

f1 создается как Baz. Однако напечатано Bar. Поскольку Bar предоставляет GetName, когда используется f1.GetName(), это метод, который вызывается - независимо от того, что Baz также реализует GetName. Причина в том, что f1 не набирается как Baz, и если бы это было так, это вызывало бы метод Baz GetName. Примером этого может быть просмотр вывода

Console.WriteLine(((Baz)f1).GetName() + "-" + f2.GetName() + "-" + f3.GetName());
//Baz-Bar-Quux

Это возможно из-за двух фактов. Во-первых, f1 первоначально был создан как Baz, он был просто набран как Bar. Во-вторых, Baz имеет метод GetName, а использование new в своем определении скрывает унаследованный метод Bar GetName, позволяющий вызывать Baz GetName.

f2.GetName()

Очень похожее типирование происходит с f2, который определяется как

IFoo f2 = new Baz();

В то время как класс Baz реализует метод GetName, он не реализует метод IFoo GetName, потому что Baz не наследует от IFoo, и поэтому метод недоступен. Bar реализует IFoo, а поскольку Baz наследует от Bar, Bar GetName - это метод, который отображается, когда f2 набирается как IFoo.

Опять же, поскольку f2 первоначально был создан как Baz, его все равно можно отличить до Baz.

Console.WriteLine(f1.GetName() + "-" + ((Baz)f2).GetName() + "-" + f3.GetName());
//Bar-Baz-Quux

И будет иметь тот же результат вывода по причине, указанной выше для f1 (f2 изначально был введен как Baz, а метод Baz GetName скрывает унаследованный метод Bar GetName)).

f3.GetName()

Разная история здесь. Quux наследует и реализует IFoo, и, скрывая Bar реализацию IFoo с помощью new. В результате метод Quux GetName - это тот, который вызывается.