Почему этот код С# возвращает то, что он делает
Может кто-то, пожалуйста, помогите мне понять, почему этот фрагмент кода возвращает "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());
}
}
Ответы
Ответ 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
- это тот, который вызывается.