Вот как вы думаете о виртуальных методах. Каждый экземпляр класса имеет «ящики» для хранения методов. Когда вы помечаете метод как virtual
, он говорит, что создайте новый «ящик» и поместите в него метод. Когда вы помечаете метод как override
в производном классе, он удерживает «коробку» от базового класса, но помещает в него новый метод.
Итак, у вас есть класс A
и метод с именем mVVirtual
, помеченный как virtual
. Это говорит: создайте новый «ящик» с именем mVVirtual
и поместите в него метод с определением
Console.WriteLine("A::mVVirtual");
Затем у вас есть производный класс B
и метод с именем mVVirtual
, помеченный как virtual
. Это говорит: создайте новый «ящик» с именем mVVirtual
и поместите в него метод с определением
Console.WriteLine("B::mVVirtual");
В частности, «коробка», унаследованная от A
, скрыта! Его нельзя увидеть объектами, которые напечатаны как B
s, или классами, производными от B
.
Затем у вас есть производный класс C
и метод с именем mVVirtual
, помеченный как override
. Это говорит: возьмите «коробку» с именем mVVirtual
, унаследованную от B
, и поместите в нее другой метод с определением
Console.WriteLine("C::mVVirtual");
Теперь, когда у вас есть
B b1 = new C();
b1.mVVirtual();
вы говорите компилятору, что b1
- это B
, так что b1.mVVirtual()
смотрит в "коробку" mVVirtual
и находит метод с определением
Console.WriteLine("C::mVVirtual");
потому что b1
на самом деле C
, и это то, что находится в «коробке» mVVirtual
для экземпляров C
.
Но когда у вас есть
A a2 = new C();
a2.mVVirtual();
вы говорите компилятору, что a2
является A
, и поэтому он выглядит в «коробке» и находит
Console.WriteLine("A::mVVirtual");
Компилятор не может знать, что a2
действительно является C
(вы ввели его как A
), поэтому он не знает, что a2
действительно является экземпляром класса, производного от класс, который скрыл «коробку» mVVirtual
, определенную A
. Он знает, что A
имеет "ящик" с именем mVVirtual
, и поэтому он генерирует код для вызова метода в этом "ящике".
Итак, чтобы попытаться выразить это кратко:
class A {
public virtual void mVVirtual() { Console.WriteLine("A::mVVirtual"); }
}
определяет класс, у которого есть «ящик» с полным именем A::mVVirtual
, но к которому вы можете обращаться по имени mVVirtual
.
class B : A
{
// "new" method; compiler will tell you that this should be marked "new" for clarity.
public virtual void mVVirtual() { Console.WriteLine("B::mVVirtual"); }
}
определяет класс, у которого есть «ящик» с полным именем B::mVVirtual
, но к которому вы можете обращаться по имени mVVirtual
. Ссылка на B.mVVirtual
не будет ссылаться на «коробку» с полным именем A::mVVirtual
; этот «ящик» не может быть виден объектами, которые напечатаны как B
s (или классами, которые происходят от B
).
class C : B
{
public override void mVVirtual() { Console.WriteLine("C::mVVirtual"); }
}
определяет класс, который принимает "коробку" с полным именем B::mVVirtual
и помещает в нее другой метод.
Тогда
A a2 = new C();
a2.mVVirtual();
говорит, что a2
- это A
, поэтому a2.mVVirtual
ищет в «коробке» полное имя A::mVVirtual
и вызывает метод в этом «ящике». Вот почему вы видите
A::mVVirtual
на консоли.
Есть еще два метода аннотирования. abstract
делает новый «ящик», не помещает определение метода в «ящик». new
создает новый «ящик» и помещает определение метода в «ящик», но не позволяет производным классам помещать свои собственные определения метода в «ящик» (используйте virtual
, если хотите это сделать) .
Извините за скучность, но я надеюсь, что это поможет.