Любой метод может быть переопределен (= virtual
) или нет. Решение принимает тот, кто определяет метод:
class Person
{
// this one is not overridable (not virtual)
public String GetPersonType()
{
return "person";
}
// this one is overridable (virtual)
public virtual String GetName()
{
return "generic name";
}
}
Теперь вы можете переопределить те методы, которые можно переопределить:
class Friend : Person
{
public Friend() : this("generic name") { }
public Friend(String name)
{
this._name = name;
}
// override Person.GetName:
public override String GetName()
{
return _name;
}
}
Но вы не можете переопределить метод GetPersonType
, потому что он не виртуальный.
Давайте создадим два экземпляра этих классов:
Person person = new Person();
Friend friend = new Friend("Onotole");
Когда не виртуальный метод GetPersonType
вызывается экземпляром Fiend
, на самом деле вызывается Person.GetPersonType
:
Console.WriteLine(friend.GetPersonType()); // "person"
Когда виртуальный метод GetName
вызывается экземпляром Friend
, вызывается Friend.GetName
:
Console.WriteLine(friend.GetName()); // "Onotole"
Когда виртуальный метод GetName
вызывается экземпляром Person
, вызывается Person.GetName
:
Console.WriteLine(person.GetName()); // "generic name"
Когда вызывается не виртуальный метод, тело метода не ищется - компилятор уже знает фактический метод, который должен быть вызван. Принимая во внимание, что с помощью виртуальных методов компилятор не может быть уверен, какой из них вызывать, и он просматривается во время выполнения в иерархии классов снизу вверх, начиная с типа экземпляра, для которого вызывается метод: для friend.GetName
он выглядит стартующим в Friend
классе и находит его сразу, для person.GetName
класса он начинается в Person
и находит его там.
Иногда вы создаете подкласс, переопределяете виртуальный метод, и вам больше не нужны переопределения в иерархии - для этого вы используете sealed override
(говоря, что вы последний, кто переопределяет метод):
class Mike : Friend
{
public sealed override String GetName()
{
return "Mike";
}
}
Но иногда ваш друг Майк решает изменить свой пол и, следовательно, свое имя на Алису :) Вы можете изменить исходный код или вместо этого подкласс Майка:
class Alice : Mike
{
public new String GetName()
{
return "Alice";
}
}
Здесь вы создаете совершенно другой метод с тем же именем (теперь у вас есть два). Какой метод и когда вызывается? Это зависит от того, как вы это называете:
Alice alice = new Alice();
Console.WriteLine(alice.GetName()); // the new method is called, printing "Alice"
Console.WriteLine(((Mike)alice).GetName()); // the method hidden by new is called, printing "Mike"
Когда вы звоните с позиции Alice
, вы звоните Alice.GetName
, когда с Mike
- вы звоните Mike.GetName
. Здесь не выполняется поиск во время выполнения, поскольку оба метода не виртуальны.
Вы всегда можете создать new
методы - независимо от того, являются ли скрытые методы виртуальными или нет.
Это относится и к свойствам и событиям - они представлены в виде методов внизу.