Menu
CoddyTech

Upcasting và downcasting

Thuộc phần Lập trình hướng đối tượng trong hành trình C# của Coddy. Bài học 29 trên 70.

Khi làm việc với các hệ thống phân cấp kế thừa, bạn thường cần chuyển đổi giữa kiểu cơ sở và kiểu dẫn xuất. Upcasting chuyển kiểu dẫn xuất thành kiểu cơ sở, còn downcasting làm ngược lại.

Chuyển kiểu lên là thao tác ngầm định và luôn an toàn vì lớp dẫn xuất chắc chắn có mọi thứ mà lớp cơ sở có:

Dog myDog = new Dog();
Animal animal = myDog;  // Ép kiểu lên (upcasting) diễn ra ngầm định và luôn thành công

Ép kiểu xuống yêu cầu ép kiểu tường minh và có thể thất bại khi chạy nếu đối tượng không thực sự thuộc kiểu mà bạn đang ép sang:

Animal animal = new Dog();
Dog dog = (Dog)animal;  // Ép kiểu xuống: thành công vì đối tượng này thực sự là Dog

Animal cat = new Cat();
Dog wrong = (Dog)cat;   // Gây ra ngoại lệ InvalidCastException!

Để ép kiểu xuống an toàn, trước tiên hãy dùng từ khóa is để kiểm tra kiểu dữ liệu, hoặc dùng as, toán tử này trả về null thay vì ném ra ngoại lệ:

if (animal is Dog d)
{
    d.Bark();  // An toàn: d chỉ có thể dùng khi bước kiểm tra thành công
}

Dog maybeDog = animal as Dog;
if (maybeDog != null)
{
    maybeDog.Bark();
}

Trong C# hiện đại, cách so khớp mẫu is được ưu tiên vì kết hợp kiểm tra kiểu dữ liệu và ép kiểu trong một bước, giúp code của bạn an toàn và gọn gàng hơn.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống kiểm tra phương tiện để tìm hiểu cách ép kiểu lên (upcasting) và ép kiểu xuống (downcasting) giúp bạn làm việc linh hoạt với các đối tượng trong hệ thống phân cấp kế thừa. Bạn sẽ tạo nhiều loại phương tiện khác nhau và viết code để xác định an toàn từng loại phương tiện cụ thể rồi tương tác với chúng.

Bạn sẽ sắp xếp code trong bốn tệp:

  • Vehicle.cs: Định nghĩa lớp cơ sở Vehicle trong không gian tên Inspection. Mỗi phương tiện có thuộc tính LicensePlate (chuỗi), được thiết lập thông qua hàm khởi tạo. Thêm phương thức tên là GetBasicInfo(), trả về "Vehicle: {LicensePlate}".
  • Car.cs: Định nghĩa lớp Car trong không gian tên Inspection, kế thừa từ Vehicle. Ô tô có thêm thuộc tính NumberOfDoors (int). Hàm khởi tạo nhận biển số và số cửa. Thêm phương thức tên là Honk(), trả về "{LicensePlate} honks: Beep beep!".
  • Motorcycle.cs: Định nghĩa lớp Motorcycle trong không gian tên Inspection, kế thừa từ Vehicle. Xe máy có thuộc tính HasSidecar (bool). Hàm khởi tạo nhận biển số và trạng thái có thùng xe bên hông hay không. Thêm phương thức tên là Rev(), trả về "{LicensePlate} revs: Vroom!".
  • Program.cs: Trong tệp chính, bạn sẽ tạo các phương tiện và thực hành cả ép kiểu lên lẫn ép kiểu xuống an toàn. Dùng các giá trị đầu vào để tạo một Car và một Motorcycle, sau đó lưu cả hai vào mảng các tham chiếu Vehicle (ép kiểu lên). Duyệt qua mảng và in thông tin cơ bản của từng phương tiện. Sau đó, dùng từ khóa is với cú pháp so khớp mẫu để ép kiểu xuống an toàn: nếu đó là Car, hãy gọi Honk(); nếu đó là Motorcycle, hãy gọi Rev().

Bạn sẽ nhận bốn giá trị đầu vào:

  • Biển số ô tô
  • Số cửa
  • Biển số xe máy
  • Có thùng xe bên hông hay không (true hoặc false)

In đầu ra theo định dạng sau cho từng phương tiện trong mảng:

{GetBasicInfo() result}
{Honk() or Rev() result based on actual type}

Ví dụ, nếu các giá trị đầu vào là ABC-123, 4, XYZ-789 và false, đầu ra sẽ là:

Vehicle: ABC-123
ABC-123 honks: Beep beep!
Vehicle: XYZ-789
XYZ-789 revs: Vroom!

Lưu ý rằng tất cả phương tiện đều được lưu dưới dạng tham chiếu Vehicle (ép kiểu lên), nhưng bạn vẫn có thể xác định an toàn kiểu thực của chúng và truy cập các phương thức dành riêng cho từng kiểu bằng cú pháp so khớp mẫu is để ép kiểu xuống!

Tự làm thử

using System;
using Inspection;

class Program
{
    public static void Main(string[] args)
    {
        // Đọc các giá trị đầu vào
        string carPlate = Console.ReadLine();
        int numberOfDoors = Convert.ToInt32(Console.ReadLine());
        string motorcyclePlate = Console.ReadLine();
        bool hasSidecar = Convert.ToBoolean(Console.ReadLine());
        
        // TODO: Tạo đối tượng (object) Car bằng carPlate và numberOfDoors
        
        // TODO: Tạo đối tượng Motorcycle bằng motorcyclePlate và hasSidecar
        
        // TODO: Tạo mảng (array) các tham chiếu Vehicle chứa cả hai phương tiện (upcasting)
        
        // TODO: Duyệt qua mảng và với mỗi phương tiện:
        //   In ra kết quả của GetBasicInfo()
        //   Dùng từ khóa (keyword) 'is' cùng với đối sánh mẫu để ép kiểu xuống an toàn:
        //     Nếu là Car, gọi Honk() và in ra kết quả
        //     Nếu là Motorcycle, gọi Rev() và in ra kết quả
    }
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch C# online