(Это ответ на вопрос «почему это разрешено», который я считаю действительно центральной точкой вашего вопроса. Как это работает с точки зрения 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 # был разработан, чтобы попытаться избежать кошмаров совместимости. Это не решает проблему полностью, но делает довольно хорошую работу.