Upcasting и Downcasting
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 29 из 70.
При работе с иерархиями наследования часто возникает необходимость преобразования между базовыми и производными типами. Восходящее преобразование (upcasting) преобразует производный тип в базовый, в то время как нисходящее преобразование (downcasting) выполняет обратное действие.
Восходящее преобразование (upcasting) является неявным и всегда безопасным, так как производный класс гарантированно содержит всё, что есть в базовом классе:
Dog myDog = new Dog();
Animal animal = myDog; // Восходящее приведение — неявное, всегда работаетПонижающее приведение типов требует явного приведения и может завершиться ошибкой во время выполнения, если объект на самом деле не является типом, к которому вы его приводите:
Animal animal = new Dog();
Dog dog = (Dog)animal; // Понижающее приведение (Downcasting) — работает, так как это на самом деле Dog
Animal cat = new Cat();
Dog wrong = (Dog)cat; // Выбрасывает исключение InvalidCastException!Для безопасного понижающего приведения типов используйте ключевое слово is, чтобы сначала проверить тип, или используйте as, которое возвращает null вместо того, чтобы вызывать исключение:
if (animal is Dog d)
{
d.Bark(); // Безопасно — d доступна только в случае успешной проверки
}
Dog maybeDog = animal as Dog;
if (maybeDog != null)
{
maybeDog.Bark();
}Подход сопоставления с образцом is предпочтителен в современном C#, поскольку он объединяет проверку типа и приведение в один шаг, делая ваш код более безопасным и чистым.
Задание
ЛегкоДавайте создадим систему техосмотра транспортных средств, которая демонстрирует, как восходящее (upcasting) и нисходящее (downcasting) приведение типов позволяют гибко работать с объектами в иерархии наследования. Вы создадите различные типы транспортных средств и напишете код, который безопасно идентифицирует конкретные виды транспорта и взаимодействует с ними.
Вы организуете свой код в четырех файлах:
Vehicle.cs: Определите базовый классVehicleв пространстве именInspection. У каждого транспортного средства есть свойствоLicensePlate(string), устанавливаемое через конструктор. Добавьте методGetBasicInfo(), который возвращает"Vehicle: {LicensePlate}".Car.cs: Определите классCarв пространстве именInspection, который наследуется отVehicle. У автомобилей есть дополнительное свойствоNumberOfDoors(int). Конструктор принимает как номерной знак, так и количество дверей. Добавьте методHonk(), который возвращает"{LicensePlate} honks: Beep beep!".Motorcycle.cs: Определите классMotorcycleв пространстве именInspection, который наследуется отVehicle. У мотоциклов есть свойствоHasSidecar(bool). Конструктор принимает номерной знак и наличие коляски. Добавьте методRev(), который возвращает"{LicensePlate} revs: Vroom!".Program.cs: В основном файле вы создадите транспортные средства и попрактикуетесь как в восходящем, так и в безопасном нисходящем приведении типов. СоздайтеCarиMotorcycle, используя входные значения, затем сохраните оба объекта в массиве ссылокVehicle(upcasting). Пройдите циклом по массиву и для каждого транспортного средства выведите его основную информацию. Затем используйте ключевое словоisс сопоставлением с образцом (pattern matching) для безопасного нисходящего приведения: если этоCar, вызовитеHonk(); если этоMotorcycle, вызовитеRev().
Вы получите четыре входных значения:
- Номерной знак автомобиля
- Количество дверей
- Номерной знак мотоцикла
- Наличие коляски (
trueилиfalse)
Выведите результат в следующем формате для каждого транспортного средства в массиве:
{GetBasicInfo() result}
{Honk() or Rev() result based on actual type}Например, если входные данные — ABC-123, 4, XYZ-789 и false, вывод должен быть следующим:
Vehicle: ABC-123
ABC-123 honks: Beep beep!
Vehicle: XYZ-789
XYZ-789 revs: Vroom!Обратите внимание, как все транспортные средства хранятся в виде ссылок Vehicle (upcasting), но вы можете безопасно определить их истинные типы и получить доступ к методам, специфичным для типа, используя синтаксис сопоставления с образцом is для нисходящего приведения!
Шпаргалка
При работе с иерархиями наследования upcasting (восходящее преобразование) преобразует производный тип в его базовый тип, в то время как downcasting (нисходящее преобразование) преобразует базовый тип в производный.
Upcasting является неявным и всегда безопасным:
Dog myDog = new Dog();
Animal animal = myDog; // Upcasting - неявное, всегда работаетDowncasting требует явного приведения и может привести к ошибке во время выполнения:
Animal animal = new Dog();
Dog dog = (Dog)animal; // Downcasting - работает, так как это действительно Dog
Animal cat = new Cat();
Dog wrong = (Dog)cat; // Выбрасывает InvalidCastException!Безопасное нисходящее преобразование с использованием ключевого слова is и сопоставления с образцом (предпочтительный вариант):
if (animal is Dog d)
{
d.Bark(); // Безопасно - d доступна только в случае успешной проверки
}Безопасное нисходящее преобразование с использованием ключевого слова as (возвращает null, если приведение не удалось):
Dog maybeDog = animal as Dog;
if (maybeDog != null)
{
maybeDog.Bark();
}Попробуйте сами
using System;
using Inspection;
class Program
{
public static void Main(string[] args)
{
// Чтение входных данных
string carPlate = Console.ReadLine();
int numberOfDoors = Convert.ToInt32(Console.ReadLine());
string motorcyclePlate = Console.ReadLine();
bool hasSidecar = Convert.ToBoolean(Console.ReadLine());
// TODO: Создайте объект Car, используя carPlate и numberOfDoors
// TODO: Создайте объект Motorcycle, используя motorcyclePlate и hasSidecar
// TODO: Создайте массив ссылок Vehicle, содержащий оба транспортных средства (upcasting)
// TODO: Пройдите циклом по массиву и для каждого транспортного средства:
// - Выведите результат GetBasicInfo()
// - Используйте ключевое слово 'is' с сопоставлением шаблонов (pattern matching) для безопасного понижения типа (downcast):
// - Если это Car, вызовите и выведите Honk()
// - Если это Motorcycle, вызовите и выведите Rev()
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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'Необязательные параметрыИменованные аргументыПерегрузка методов