Chào các bạn!

Hôm nay mình xin trở lại với chuỗi bài hướng dẫn về Lập trình hướng đối tượng OOP.  Trong C#, một class con có thể chứa các hàm cùng tên với các hàm trong class mà nó kế thừa. Điều đó là tốt đối với lập trình viên bởi họ có thể tận dụng điều đó để cài đặt mã nguồn của mình theo OOP, tuy nhiên nó đôi khi sẽ gây nhầm lẫn.
Bạn có thể định rõ ra cách mà hàm đó tương tác, hoạt động như thế nào bằng cách sử dụng từ khóa new hoặc override



Sự khác nhau được mô tả trong bài dưới đây:

Trong một chương trình Console, khai báo hai class: BaseClass DerivedClass kế thừa từ BaseClass.


Trong hàm Main, khai báo các biến bc, dc, bcdc tương tướng với:
  • bc là đối tượng kiểu BaseClass, và mang giá trị kiểu BaseClass
  • dc là đối tượng kiểu DerivedClass, và mang giá trị kiểu DerivedClass
  • bcdc là đối tượng kiểu BaseClass và mang giá trị kiểu DerivedClass: Đây là biến cần chú ý tới nhiều nhất!
Bởi vì bcbcdc điều có kiểu dữ liệu là BaseClass, nó chỉ nó thể truy cập trực tiếp đến Method1() (ngoại trừ khi bạn dùng casting ép kiểu nó về DerivedClass - việc này là có thể được).  Biến dc có thể truy cập đến cả Method1()Method2(). Những mối quan hệ trên được thể hiện như hình bên dưới:


Ta thấy kết quả là những gì có thể đoán trước được. Tiếp tục ta thêm một hàm Method2() vào BaseClass. Bây giờ ta đã có hai hàm trùng tên, một nằm ở class cha và một nằm ở class con.


Bởi vì BaseClass bây giờ đã có Method2(), ta sẽ gọi được hàm Method2() ở các biến mạng kiểu là BaseClass là bc và bcdc:


Khi bạn build project, bạn sẽ thấy một thông báo cảnh cáo. Dòng warning nói rằng Method2() ở DerivedClass "che" đi Method2() ở BaseClass. Bạn cũng được khuyên nên sử dụng từ khóa new ở phần cài đặt/định nghĩa của Method2() nếu bạn muốn tạo ra kết quả tượng tự như vậy. Hoặc bạn có thể đổi tên Method2() để resolve warning đó, nhưng nó không phải lúc nào cũng tốt.

Trước khi thêm từ khóa new, chạy chương trình và xem output tạo ra bởi những hàm đã gọi, bạn sẽ nhận được như sau:


Hãy chú ý đến những dòng được mình highlight, dòng thứ 2 được bc mang kiểu BaseClass nên việc nó chỉ gọi hàm Method2() của lớp cha cũng không có gì là lạ. Ở hai dòng cuối, hành vi mặc định của nó được giữ nguyên, nên nó ưu tiên gọi hàm từ lớp cha dù ở lớp con cũng có một hàm Method2() cùng tên.
Từ khóa new "bảo tồn" mối quan hệ mà tạo ra kết quả ban đầu, nhưng nó vô hiệu hóa đi các cảnh bảo từ IDE. Các biến có kiểu là BaseClass vẫn giữ nguyên hành vi của nó và tiếp tục truy cập các hàm/thuộc tính của BaseClass, và các biến có kiểu là DerivedClass tiếp tục tuy cập vào các hàm/thuộc tính của DerivedClass đầu tiên, sau đó mới suy xét đến những thành phần mà nó kế thừa từ BaseClass.

Ví dụ: 
Ta có thể thêm từ khóa new trước hoặc sau public

Chạy lại chương trình để chắc rằng output không thay đổi. Cũng để thấy rằng những warnings từ IDE không còn xuất hiện nữa.
Bằng cách sử dụng từ khóa new, bạn thầm khẳng định rằng bạn biết hàm/thuộc tính đó "che" đi hàm/thuộc tính mà nó kế thừa từ chính lớp cha của nó. Để biết thêm thông tin về ẩn tên (name hiding) trong kế thừa, xem new Modifier

Mặc khác, để tạo ra 1 hành vi tương phản lại ở trên, ta sử dụng từ khóa override. Thêm hàm sau kèm từ khóa override vào DerivedClass và từ khóa virtual vào tên hàm ở BaseClass
Từ khóa override có thể đặt trước hoặc sau public


Chạy lại chương trình, ta chú ý sự khác nhau giữa 2 dòng cuối của output với nhau, và so với output của lần test trước. Qua đó thấy rõ sự khác nhau của override new.


Ta thấy khi gọi hàm Method1() trùng tên giữa 2 lớp, mang từ khóa override từ biến bcdc lần này nó không gọi hàm của lớp cha nữa, mà chuyển sang gọi hàm của lớp con. Tuy nhiên cũng là gọi hàm trùng tên Method2(), không có từ khóa override thì nó sẽ trở lại hành vi mặc định là gọi hàm từ lớp cha.

Việc sử dụng từ khóa override làm cho biến bcdc có thể truy cập đến Method1() của lớp con DerivedClass. Thông thường, đây mới là hành vi được các lập trình viên chúng ta mong muốn khi sử dụng OOP và kế thừa. Bạn luôn muốn những object được khởi tạo như một thể hiện của lớp con sử dụng các hàm từ chính lớp con đó chứ không phải những hàm cùng tên, được cài đặt từ trước ở lớp cha. 

Tuy nhiên hành động mặc định của C# lại ưu tiên lớp cha hơn :D. Vậy nên bạn có thể đạt được mong muốn của mình bằng cách phải dùng từ khóa override để extend và ghi đè những hàm của lớp cha bằng những hàm mới cùng tện trong lớp con.

Full source code:


Đến đây các bạn đã hiểu về sự khác nhau giữa new và override trong C# chưa nào? 
Bạn có thể tham khảo thêm ví dụ sau đây để biết rõ hơn về chúng. Mọi thắc mắc xin để lại bình luận bên dưới nhé.

Chào các bạn, chúc các bạn học tốt!