Разница заключается в том, как каждый метод вызывается из каждого места, и сводится к различиям между new
и virtual
/ override
.
Сначала теория, упрощенное объяснение обоихключевые слова:
new
просто определяет другой метод в производном классе с тем же именем существующего метода в базовом классе, «скрывая» его.Выбор метода для вызова (базовый или производный) выполняется в время компиляции , в зависимости от типа ссылки, используемой для вызова метода. virtual
указывает, что методможет иметь альтернативную реализацию в производном классе, и в этом случае его следует использовать вместо этого.Здесь выбор делается в время выполнения в зависимости от типа реального объекта.
Теперь примените его к вашему случаю.Все вызовы A
здесь абсолютно одинаковы, поскольку они виртуальны, и единственный находящийся экземпляр имеет тип Sub
.Динамическая диспетчеризация делает свое дело, и это приводит к вызову на Sub.B
, как вы нашли.
Но вызовы на B
находятся в двух местах.Один внутри метода print
, а другой непосредственно main
.Поскольку B
не virtual
, он использует статическую диспетчеризацию и тип ссылки время компиляции для определения сайта вызова.Один из main
достаточно прост, чтобы понять, почему он использует Sub.B
.Другой в методе print
, однако, не использует те же ссылки, он вызывает метод экземпляра в том же классе, неявно используя указатель this
.Это полностью эквивалентно написанию этого:
public void print()
{
this.A();
this.B();
}
Так что вызов B
полностью зависит от типа времени компиляции this
, то есть Base
в данном случае (как написано в этом классе).Поэтому здесь вызывается Base.B
.
Тот факт, что предыдущий вызов print
поступил из другого типа переменной, здесь не имеет значения, поскольку он используется только для определения того, какую реализацию print
взять (здесь мытолько один), но какие бы действия ни выполнялись самим методом, они выходят за его пределы и поэтому не влияют на его поведение.