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.
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ởVehicletrong không gian tênInspection. Mỗi phương tiện có thuộc tínhLicensePlate(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ớpCartrong không gian tênInspection, kế thừa từVehicle. Ô tô có thêm thuộc tínhNumberOfDoors(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ớpMotorcycletrong không gian tênInspection, kế thừa từVehicle. Xe máy có thuộc tínhHasSidecar(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ộtCarvà mộtMotorcycle, sau đó lưu cả hai vào mảng các tham chiếuVehicle(é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óaisvới cú pháp so khớp mẫu để ép kiểu xuống an toàn: nếu đó làCar, hãy gọiHonk(); nếu đó làMotorcycle, hãy gọiRev().
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 (
truehoặcfalse)
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ả
}
}
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
1Các khái niệm cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiKhông gian tên và chỉ thịGiới thiệu về lớp và đối tượngTừ khóa thisPhương thức và tham sốTrường và thuộc tínhHàm khởi tạoKhởi tạo đối tượngÔn tập: Máy tính đơn giản4Kế thừa
Cú pháp kế thừa cơ bản (:)Từ khóa 'base'Từ khóa virtual và overrideLớp sealedLớp cơ sở 'object'Ôn tập: hệ phân cấp nhân viên7Tính năng nâng cao
Nạp chồng toán tửBộ lập chỉ mục (this[])Ghi đè ToString()Phương thức mở rộngÔn tập: Danh sách tùy chỉnh10Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếSingleton an toàn với luồngMẫu FactoryMẫu Observer (sự kiện)Mẫu Strategy2Thuộc tính và thành viên tĩnh
Thuộc tính tự độngThuộc tính chỉ đọc hoặc chỉ ghiTrường và phương thức tĩnhLớp tĩnhThành viên có thân biểu thức5Đa hình và interface
Đa hình lúc biên dịch và lúc chạyInterface và lớp trừu tượngNhiều interfaceInterface tường minhUpcasting và downcastingÔn tập: bộ tính toán hình học8Các khái niệm OOP nâng cao
Ưu tiên composition hơn kế thừaGenerics (lớp và phương thức)Delegate và sự kiệnAttribute và reflectionGiao diện IDisposable và câu lệnh usingKiến thức cơ bản về dependency injection11Các mẫu thiết kế phần 2
Mẫu lệnhMẫu bộ chuyển đổiMẫu trang tríMẫu phương thức khuôn mẫuMẫu trạng tháiMẫu tổ hợpTự luyện tập: Trình biên dịch C# online