Ключевые слова Virtual и Override
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 21 из 70.
Когда производный класс наследует метод, иногда требуется полностью изменить поведение этого метода. Ключевые слова virtual и override работают вместе, чтобы сделать это возможным контролируемым образом.
Пометьте метод как virtual в базовом классе, чтобы указать, что производным классам разрешено переопределять его реализацию:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Some sound");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}Ключевое слово override в производном классе сообщает компилятору, что вы намеренно заменяете базовый метод. Без virtual в базовом методе вы не можете использовать override.
Настоящая мощь проявляется тогда, когда вы используете ссылку на базовый класс для хранения производного объекта:
Animal myPet = new Dog();
myPet.Speak(); // Вывод: Woof!Несмотря на то, что myPet объявлена как Animal, C# вызывает версию Speak() класса Dog. Это называется полиморфизмом времени выполнения — фактически вызываемый метод зависит от истинного типа объекта, а не от объявленного типа переменной.
Вы все еще можете получить доступ к базовой реализации, используя base.Speak() внутри вашего переопределения, если вы хотите расширить, а не полностью заменить поведение.
Задание
ЛегкоДавайте создадим систему уведомлений, которая демонстрирует, как производные классы могут полностью переопределять поведение базового класса с помощью ключевых слов virtual и override, а также покажем мощь полиморфизма времени выполнения.
Вы создадите три файла для организации вашего кода:
Notification.cs: Определите классNotificationв пространстве именAlerts. Этот базовый класс представляет собой общее уведомление со свойствомMessage(string) и конструктором, который его устанавливает. Включитеvirtualметод с именемSend(), который возвращает"Sending notification: {Message}".EmailNotification.cs: Определите классEmailNotificationв пространстве именAlerts, который наследуется отNotification. Добавьте свойствоRecipient(string) и конструктор, принимающий как сообщение, так и получателя (используйтеbaseдля передачи сообщения). Переопределите методSend(), чтобы он возвращал"Emailing {Recipient}: {Message}".SmsNotification.cs: Определите классSmsNotificationв пространстве именAlerts, который также наследуется отNotification. Добавьте свойствоPhoneNumber(string) и конструктор, принимающий как сообщение, так и номер телефона. ПереопределитеSend(), чтобы он возвращал"SMS to {PhoneNumber}: {Message}".Program.cs: В основном файле создайте экземпляры всех трех типов уведомлений, используя входные значения. Затем продемонстрируйте полиморфизм времени выполнения, сохранив все три экземпляра в переменных типаNotificationи вызвавSend()для каждого из них.
Вы получите четыре входных значения:
- Сообщение уведомления
- Адрес электронной почты получателя
- Номер телефона для SMS
Выведите результат вызова Send() для каждого уведомления, сохраненного как ссылка на базовый класс:
{base notification Send() result}
{email notification Send() result}
{sms notification Send() result}Например, если входные данные: Meeting at 3pm, alice@email.com и 555-1234, вывод должен быть следующим:
Sending notification: Meeting at 3pm
Emailing alice@email.com: Meeting at 3pm
SMS to 555-1234: Meeting at 3pmОбратите внимание, что хотя все три объекта хранятся в переменных типа Notification, каждый из них вызывает свою собственную переопределенную версию Send(). Это и есть полиморфизм времени выполнения в действии!
Шпаргалка
Используйте ключевое слово virtual в базовом классе, чтобы пометить метод, который производные классы могут переопределить:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Some sound");
}
}Используйте ключевое слово override в производном классе, чтобы заменить реализацию метода базового класса:
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}Полиморфизм времени выполнения возникает, когда ссылка на базовый класс содержит производный объект — вызываемый метод зависит от истинного типа объекта, а не от объявленного типа переменной:
Animal myPet = new Dog();
myPet.Speak(); // Output: Woof!Вы можете получить доступ к базовой реализации внутри переопределения, используя base:
public override void Speak()
{
base.Speak(); // Вызывает версию базового класса
Console.WriteLine("Woof!");
}Попробуйте сами
using System;
using Alerts;
class Program
{
public static void Main(string[] args)
{
// Чтение входных данных
string message = Console.ReadLine();
string email = Console.ReadLine();
string phone = Console.ReadLine();
// TODO: Создайте экземпляры всех трех типов уведомлений
// Сохраните их в переменные типа Notification для демонстрации полиморфизма
// TODO: Создайте базовое уведомление Notification
Notification baseNotification;
// TODO: Создайте EmailNotification (сохраненное как тип Notification)
Notification emailNotification;
// TODO: Создайте SmsNotification (сохраненное как тип Notification)
Notification smsNotification;
// TODO: Вызовите Send() для каждого и выведите результаты
// Переопределенные методы будут вызваны благодаря полиморфизму времени выполнения
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методов