Что на самом деле происходит с новым и перекрывающим? - PullRequest
1 голос
/ 05 августа 2009

Я нашел множество практических примеров этого и понимаю практический вывод при переопределении или сокрытии методов, но я ищу некоторые из них под информацией о том, почему это так и почему C # позволяет это, когда в соответствии с правилами с полиморфизмом это не должно быть разрешено - по крайней мере, насколько мое понимание полиморфизма идет (что, кажется, совпадает со стандартными определениями, найденными в Википедии / Webopedia).

Class Base
{
    public virtual void PrintName()
    {
        Console.WriteLine("BaseClass");
    }
}

Class FirstDerived : Base
{
    public override void PrintName()
    {
        Console.WriteLine("FirstDerived");
    }
}

Class SecondDerived : Base
{
    public new void PrintName()
    {
        Console.WriteLine("SecondDerived");
    }
}

Используя следующий код:

FirstDerived b = new FirstDerived();
BaseClass a = b;
b.PrintName();
a.PrintName();

Я получаю:

FirstDerived FirstDerived

Хорошо, я понял, имеет смысл.

SecondDerived c = new SecondDerived();
BaseClass a = c;
c.PrintName();
a.PrintName();

Я получаю:

SecondDerived 
BaseClass

Хорошо, это тоже имеет смысл, экземпляр a не может видеть c.PrintName (), поэтому он использует свой собственный метод для печати своего собственного имени, однако я могу привести свой экземпляр к его истинному типу, используя:

((SecondDerived)a).PrintName();

или

(a as SecondDerived).PrintName();

чтобы получить вывод, я ожидал:

SecondDerived

Так что же происходит под одеялом и что это означает с точки зрения полиморфизма? Мне сказали, что это средство «ломает полиморфизм» - и, согласно определению, оно так и есть. Это правильно? Может ли «объектно-ориентированный» язык, такой как C #, действительно позволить вам нарушить один из основных принципов ООП?

Ответы [ 3 ]

7 голосов
/ 05 августа 2009

(Это ответ на вопрос «почему это разрешено», который я считаю действительно центральной точкой вашего вопроса. Как это работает с точки зрения IL, мне менее интересно ... позвольте мне знаю, если вы хотите, чтобы я углубился в это. По сути, это всего лишь случай указания метода для вызова с токеном другого типа.)

Позволяет развиваться базовым классам, не нарушая производных классов.

Предположим, у Base изначально не было метода PrintName. Единственный способ получить значение SecondDerived.PrintName - это иметь выражение со статическим типом SecondDerived и вызывать его для этого. Вы отправляете свой продукт, все в порядке.

Теперь перенесемся на Base, вводя метод PrintName. Это может иметь или не иметь одинаковую семантику SecondDerived.PrintName - безопаснее предположить, что это не так.

Любые абоненты Base.PrintName знают, что они вызывают новый метод - они не могли вызвать его раньше. Любые абоненты, которые ранее использовали SecondDerived.PrintName все еще , хотят использовать его - они не хотят внезапно завершить вызов Base.PrintName, который может сделать что-то совершенно другое.

Сложность заключается в новых вызывающих абонентах SecondDerived.PrintName, которые могут или не могут оценить, что это не является переопределением Base.PrintName. Конечно, они могут заметить это из документации, но это может быть неочевидно. Однако, по крайней мере, мы не нарушили существующий код.

Когда SecondDerived перекомпилируется, авторы будут предупреждены, что теперь есть класс Base.PrintName через предупреждение. Они могут либо придерживаться своей существующей не виртуальной схемы, добавив модификатор new, либо переопределить метод Base.PrintName. Пока они не примут это решение, они будут получать предупреждение.

Версии и совместимость обычно не упоминаются в теории ОО по моему опыту, но C # был разработан, чтобы попытаться избежать кошмаров совместимости. Это не решает проблему полностью, но делает довольно хорошую работу.

4 голосов
/ 05 августа 2009

Я отвечаю «как» это работает. Джон ответил на вопрос «почему».

Вызовы к virtual методам разрешаются немного иначе, чем к не virtual. По сути, объявление метода virtual вводит «виртуальный слот метода» в базовый класс. Слот будет содержать указатель на фактическое определение метода (а содержимое будет указывать на переопределенную версию в производных классах, и новый слот не будет создан). Когда компилятор генерирует код для вызова виртуального метода, он использует инструкцию callvirt IL, определяющую слот метода для вызова. runtime отправит вызов соответствующему методу. С другой стороны, не виртуальный метод вызывается с инструкцией call IL, которая будет статически разрешена компилятором в фактический метод во время компиляции (только со знанием типа переменной времени компиляции ). Модификатор new ничего не делает в скомпилированном коде. По сути, он говорит компилятору C # «Чувак, заткнись! Я уверен, что я делаю правильные вещи» и отключает предупреждение компилятора.

Метод new (фактически любой метод без модификатора override) вводит совершенно отдельную цепочку методов (новый слот метода). Обратите внимание, что new метод может быть virtual сам по себе. Компилятор проверяет статический тип переменной, когда он хочет разрешить цепочку методов , а время выполнения выберет фактический метод в этой конкретной цепочке.

0 голосов
/ 05 августа 2009

Согласно определению Википедии :

Тип полиморфизма в объектно-ориентированном программирование - это способность одного типа A, чтобы отображаться как и использоваться как другой тип, B

Позже на той же странице:

Переопределение метода - это когда подкласс заменяет реализацию одного или больше методов его родителя. ни перегрузка метода, ни метод переопределение сами по себе реализации полиморфизма.

Тот факт, что SecondDerived не предоставляет переопределение для PrintName, не влияет на его способность появляться и использоваться в качестве базы. Предоставленная им новая реализация метода не будет использоваться везде, где экземпляр SecondDerived рассматривается как экземпляр Base; он будет использоваться только тогда, когда этот экземпляр явно используется как экземпляр SecondDerived.

Более того, SecondClass может на самом деле явно реализовывать Base.PrintName в дополнение к новой реализации скрытия, обеспечивая тем самым свое собственное переопределение, которое будет использоваться, когда рассматривается как Base. (Хотя Base должен быть явным определением интерфейса или должен быть производным от него, чтобы это разрешить)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...