Ответ
Виртуальные методы — это механизм поддержки полиморфизма в C#, позволяющий производному классу предоставить свою собственную реализацию метода, уже определенного в базовом классе. Выбор конкретной реализации происходит во время выполнения (позднее связывание) на основе фактического типа объекта.
Как это работает?
- В базовом классе метод помечается ключевым словом
virtual. - В производном классе метод переопределяется с помощью ключевого слова
override. - При вызове метода у ссылки типа базового класса, которая ссылается на объект производного класса, будет выполнена переопределенная версия.
Практический пример:
public class NotificationSender
{
// Виртуальный метод с реализацией по умолчанию
public virtual void Send(string message)
{
Console.WriteLine($"[Base Sender] Отправка базового уведомления: {message}");
}
// Невиртуальный метод. Его нельзя переопределить, можно только скрыть (new).
public void Log(string action)
{
Console.WriteLine($"[Base Log] Действие: {action}");
}
}
public class EmailSender : NotificationSender
{
// Переопределение виртуального метода
public override void Send(string message)
{
// Можно вызвать базовую реализацию, если нужно
// base.Send(message);
Console.WriteLine($"[Email] Отправлено на email: {message}");
}
}
public class SmsSender : NotificationSender
{
public override void Send(string message)
{
Console.WriteLine($"[SMS] Отправлено SMS: {message}");
}
// Скрытие (shadowing) метода базового класса, а не переопределение.
public new void Log(string action)
{
Console.WriteLine($"[SMS Log] {action}");
}
}
// Использование:
NotificationSender sender1 = new EmailSender();
NotificationSender sender2 = new SmsSender();
NotificationSender sender3 = new NotificationSender();
// Полиморфный вызов. Реализация определяется реальным типом объекта.
sender1.Send("Заказ создан"); // Вывод: [Email] Отправлено на email: Заказ создан
sender2.Send("Заказ создан"); // Вывод: [SMS] Отправлено SMS: Заказ создан
sender3.Send("Заказ создан"); // Вывод: [Base Sender] Отправка базового уведомления: Заказ создан
// Важно: для невиртуальных методов вызов определяется типом ссылки.
sender2.Log("test"); // Вывод: [Base Log] Действие: test (т.к. ссылка типа NotificationSender)
((SmsSender)sender2).Log("test"); // Вывод: [SMS Log] test
Ключевые отличия virtual/override от new: |
Аспект | virtual / override |
new (скрытие) |
|---|---|---|---|
| Связывание | Позднее (runtime) | Раннее (compile-time) | |
| Полиморфизм | Поддерживает | Не поддерживает | |
| Вызов через ссылку базового класса | Вызывается метод производного класса | Вызывается метод базового класса |
Зачем это нужно? Виртуальные методы — основа для реализации шаблонов проектирования (например, Template Method), создания расширяемых библиотек и фреймворков, где поведение базовых классов должно настраиваться наследниками.
Ответ 18+ 🔞
Давай разжую эту тему про виртуальные методы, чтобы было понятно даже тому, кто только вчера узнал, что C# — это не только решётка перед цифрами.
Представь себе, что у тебя есть базовый класс — этакий отец-основатель, у которого есть некая фишка, метод. Но он такой: «Я, конечно, могу сделать это по-своему, но если моим наследникам — твоим классам-сыночкам — не понравится, пусть делают как хотят». Вот чтобы это зафиксировать, он помечает свой метод словом virtual. Это как дать добро на вольную интерпретацию.
А теперь появляется его наследник, молодой и дерзкий. Он смотрит на метод отца и говорит: «Да ну нахуй, я сделаю это лучше и по-своему!». И чтобы все, включая компилятор, поняли, что это осознанное переопределение, а не случайное совпадение имён, он ставит ключевое слово override. И вот тут начинается магия полиморфизма, или, как я это называю, «объебономика времени выполнения».
Суть в чём: когда ты вызываешь метод у ссылки типа «папа» (NotificationSender), которая на самом деле указывает на объект типа «сын» (EmailSender), система смотрит не на то, КАКОЙ тип у ссылки, а на то, КАКОЙ тип у реального объекта в памяти. И если там есть override, она запускает именно его. Это и есть позднее связывание — решение принимается в рантайме, прямо во время работы программы. Красота, да?
NotificationSender sender = new EmailSender(); // Ссылка - папа, объект - сын.
sender.Send("Привет"); // Вызовется EmailSender.Send, потому что он override.
А теперь про new, он же скрытие. Это когда наследник такой хитрожопый: «Я вроде как сделаю метод с таким же именем, как у отца, но чтобы мой старик не догадался, что я его игнорирую». По факту, он просто создаёт СОВСЕМ НОВЫЙ метод, который никак не связан с родительским. И когда ты вызываешь его через ссылку типа «папа», компилятор, ещё на этапе сборки, говорит: «Ага, ссылка типа NotificationSender, значит, берём метод из NotificationSender». И всё. Никакой магии, никакого полиморфизма. Раннее связывание, тупо и в лоб.
NotificationSender sender = new SmsSender(); // Ссылка - папа, объект - сын с new-методом.
sender.Log("test"); // Вызовется NotificationSender.Log, потому что new — это не переопределение, а сокрытие.
((SmsSender)sender).Log("test"); // А вот так вызовется уже SmsSender.Log, потому что мы сказали компилятору смотреть на реальный тип.
Итоговая таблица, чтобы не ебал мозг:
| Что сравниваем | virtual / override |
new (скрытие) |
|---|---|---|
| Когда выбирается? | В момент выполнения (runtime), смотря на РЕАЛЬНЫЙ тип объекта. | В момент компиляции (compile-time), смотря на ТИП ССЫЛКИ. |
| Это полиморфизм? | Да, ёпта! Это его суть. | Нет, блядь. Это просто два разных метода с одним именем. |
| Вызов через папу | Запустится метод сына (если он override). | Запустится метод папы (потому что сын его просто скрыл). |
Зачем это всё, спросишь? Да всё просто. Без virtual и override не было бы ни нормальных библиотек, ни фреймворков. Представь, ты пишешь игровой движок с классом Enemy. У него метод Update(). Ты хочешь, чтобы для каждого типа врага (зомби, пришелец, начальник) логика обновления была своя. Помечаешь метод virtual, а в классах Zombie и Alien переопределяешь его через override. И движок, вызывая enemy.Update(), даже не парится, кто там конкретно — он знает, что вызовется правильная реализация. Это и есть красота и мощь, а не хухры-мухры.