Menu
Coddy logo textTech

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#, поскольку он объединяет проверку типа и приведение в один шаг, делая ваш код более безопасным и чистым.

challenge icon

Задание

Легко

Давайте создадим систему техосмотра транспортных средств, которая демонстрирует, как восходящее (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()
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование