Ответ
Переопределение (override) в C# — это механизм, позволяющий производному классу предоставить свою собственную реализацию виртуального или абстрактного метода, объявленного в базовом классе. Реализация основана на таблице виртуальных методов (Virtual Method Table, VMT).
Синтаксис и правила:
public class LoggerBase
{
// 1. Метод должен быть помечен как virtual, abstract или уже override
public virtual void Log(string message)
{
Console.WriteLine($"[BASE] {DateTime.Now}: {message}");
}
}
public class FileLogger : LoggerBase
{
// 2. Сигнатура метода (имя, тип возвращаемого значения, параметры) должна полностью совпадать
// 3. Модификатор доступа должен быть таким же или менее строгим
public override void Log(string message)
{
// 4. Можно обратиться к реализации базового класса через base
base.Log(message); // Вызовет LoggerBase.Log
// Новая логика для записи в файл
File.AppendAllText("log.txt", message + "n");
}
}
Что происходит под капотом?
- Класс
LoggerBaseимеет в своей VMT запись, указывающую на реализациюLoggerBase.Log. - Класс
FileLoggerсоздаёт свою VMT. Для методаLogзапись указывает на новую реализацию (FileLogger.Log). - При вызове
logger.Log(...)гдеloggerимеет типLoggerBase, но ссылается на объектFileLogger, CLR смотрит в VMT реального типа объекта (FileLogger) и вызывает соответствующую реализацию. Это и есть полиморфизм.
Отличия от сокрытия (new):
public class A { public void Method() => Console.WriteLine("A"); }
public class B : A { public new void Method() => Console.WriteLine("B"); } // Сокрытие
A obj = new B();
obj.Method(); // Выведет "A"! Выбор метода определяется типом переменной (A), а не объекта.
((B)obj).Method(); // Выведет "B"
Ключевые моменты:
sealed override— запрещает дальнейшее переопределение метода в цепочке наследования.- Переопределять можно только виртуальные члены. Попытка переопределить невиртуальный метод приведёт к ошибке компиляции.
- Свойства и индексаторы также могут быть виртуальными и переопределяемыми.
Ответ 18+ 🔞
А, переопределение методов! Ну это ж классика, как водка под селёдку — без этого нихуя не работает нормально. Смотри, объясню на пальцах, а то в учебниках такое заумно пишут, что мозг вытекает.
Представь, есть у тебя базовый класс, типа общего предка. Ну, как твой дед, который всем рассказывает, как надо правильно картошку сажать. У него метод Log есть, виртуальный:
public class LoggerBase
{
public virtual void Log(string message)
{
Console.WriteLine($"[BASE] {DateTime.Now}: {message}");
}
}
Вот этот virtual — это как дед сказал: «Внуки, я в целом знаю, как лог писать, но если вы придумаете что умнее — валяйте, делайте по-своему, я не обижусь».
И тут появляется его продвинутый внук-айтишник, FileLogger:
public class FileLogger : LoggerBase
{
public override void Log(string message)
{
base.Log(message); // Это типа «деда, ты тоже скажи своё слово»
File.AppendAllText("log.txt", message + "n"); // А это уже своя, новая фишка
}
}
Ключевое слово override — это как внук говорит: «Дед, я твой метод уважаю, но ща сделаю круче, с записью в файл». И система это понимает и разрешает.
А теперь самое важное, где все путаются — что под капотом-то происходит?
Есть эта самая таблица виртуальных методов (VMT). Это как общая шпаргалка для всех объектов класса. Когда ты создаёшь FileLogger, система смотрит: «Так, у этого парня есть переопределённый метод Log — окей, в таблицу записываем адрес его новой реализации, а не дедовой».
И когда ты вызываешь метод через ссылку на базовый класс:
LoggerBase logger = new FileLogger();
logger.Log("Тестовое сообщение"); // Вызовется FileLogger.Log!
Система не смотрит на тип переменной (LoggerBase). Она лезет в таблицу реального объекта (который FileLogger), находит там свежую, актуальную реализацию и запускает её. Это и есть полиморфизм, ёпта! Всё работает так, как и должно — умно и правильно.
А теперь смотри, чем это НЕ является:
Есть ещё такая пакость — сокрытие методов через new. Вот это реально подстава.
public class A { public void Method() => Console.WriteLine("A"); }
public class B : A { public new void Method() => Console.WriteLine("B"); }
A obj = new B();
obj.Method(); // Выведет "A"! Охуеть, да?
((B)obj).Method(); // А вот тут уже "B"
Видишь? Слово new — это как внук взял и сделал метод с таким же названием, но деду не сказал. И когда ты обращаешься к объекту как к «деду» (тип A), то вызывается старый метод. А чтобы вызвать новый, надо прямо указать, что ты работаешь с «внуком» (тип B). Полная хуйня, если хотел полиморфизма. Это не переопределение, а просто два разных метода, которые случайно называются одинаково. Путаница и пиздец.
Итоговые правила, чтобы не облажаться:
- Переопределять (
override) можно только то, что помечено какvirtual,abstractили уже являетсяoverride. - Сигнатура (имя, параметры, возвращаемый тип) должна совпадать точь-в-точь.
- Можно позвать реализацию предка через
base.Метод(), если надо. - Если переопределил и хочешь, чтобы дальше по цепочке наследования этот метод уже НЕ трогали — пометь его
sealed override. Поставил точку, тема закрыта. - Переопределять можно не только методы, но и свойства, и индексаторы. Главное, чтобы они тоже были виртуальными.
Вот и вся магия. Если короче: virtual/override — для честного, умного полиморфизма. new — для сокрытия, когда надо создать отдельный, независимый метод, но обычно это признак кривого дизайна. Выбирай первый вариант, и будет тебе счастье.