CodeGym /Các khóa học /C# SELF /Ghi đè phương thức (Method Overriding) trong C#

Ghi đè phương thức (Method Overriding) trong C#

C# SELF
Mức độ, Bài học
Có sẵn

1. Giới thiệu

Nếu nghe từ ghi đè mà bạn hơi toát mồ hôi thì đừng lo — nó không đáng sợ đâu, mà còn cực kỳ tiện lợi ấy! Ghi đè phương thức là khả năng thay đổi hành vi của phương thức trong class cha bằng cách tự định nghĩa lại trong class con. Nhờ vậy, code của mình linh hoạt, dễ mở rộng và sẵn sàng cho thực tế, nơi mà mỗi con vật chắc chắn không muốn chỉ là "một âm thanh nào đó".

Để cho phép ghi đè phương thức, class cha đánh dấu nó bằng từ khóa virtual. Class con, để thay đổi hiện thực, dùng từ khóa override.

Ví dụ


public class Animal
{
    public string Name { get; set; }
    public virtual void MakeSound()
    {
        Console.WriteLine("Một âm thanh chung chung của động vật...");
    }
}
        

Giờ trong class chó nè:


public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Gâu-gâu!");
    }
}
        
  • Trong class cha — virtual.
  • Trong class con — override.
  • Chữ ký phương thức (tên, kiểu trả về, tham số) phải giống hệt nhau.

Sơ đồ trực quan

Dog kế thừa Animal và có thể ghi đè MakeSound()

2. Demo hoạt động

Cùng xem ghi đè hoạt động ra sao nhé. Tạo vài con vật và kiểm tra âm thanh:

Animal pet1 = new Animal { Name = "Động vật không tên" };
Dog pet2 = new Dog { Name = "Barbos" };

pet1.MakeSound(); // In ra: Một âm thanh chung chung của động vật...
pet2.MakeSound(); // In ra: Gâu-gâu!

Giờ thử nâng cấp bài toán:
Nếu mình lưu chó vào biến kiểu Animal thì sao nhỉ?

Animal pet3 = new Dog { Name = "Sharik" };
pet3.MakeSound(); // ???

Bạn nghĩ chuyện gì sẽ xảy ra?
Đáp án: Sẽ in ra "Gâu-gâu!"
Vì dù biến là kiểu Animal, nó vẫn "trỏ" đến object Dog, nên sẽ gọi phiên bản đã ghi đè!
Đó chính là phép màu của dynamic (hay late) binding.

3. Dùng từ khóa base khi ghi đè

Đôi khi bạn không muốn thay đổi hoàn toàn phương thức, mà chỉ muốn mở rộng nó — ví dụ, thêm gì đó của mình rồi vẫn thực hiện hành vi cũ. Lúc này dùng từ khóa base. Nó cho phép gọi phiên bản phương thức từ class cha.


public class Cat : Animal
{
    public override void MakeSound()
    {
        base.MakeSound(); // gọi hiện thực ở class cha
        Console.WriteLine("Meo meo!");
    }
}
        

Khi gọi phương thức này, đầu tiên sẽ in "Một âm thanh chung chung của động vật...", sau đó là "Meo meo!"

4. Cách chọn phương thức khi ghi đè

Để dễ hình dung chuyện gì diễn ra "bên dưới", tưởng tượng có bảng như này (virtual dispatch):

Kiểu biến Kiểu object Phương thức nào được gọi
Animal Animal Animal.MakeSound
Animal Dog Dog.MakeSound
Animal Labrador Labrador.MakeSound
Dog Labrador Labrador.MakeSound
Dog Dog Dog.MakeSound

Quy tắc chính:
Kiểu biến chỉ quan trọng với compiler, còn khi chạy thì xét kiểu thực tế của object (cái mình tạo bằng new).
Cơ chế này gọi là dynamic (hay late) binding — chính nó tạo nên polymorphism (bài sau sẽ nói kỹ hơn!).

Tại sao cần ghi đè phương thức

  • Trong GUI framework: bạn có class cha là cửa sổ, rồi ghi đè phương thức để vẽ từng thành phần cụ thể.
  • Trong game engine: class cha là Enemy, các class con hiện thực các kiểu hành vi khác nhau.
  • Trong unit test: có thể tạo "stub" (stubs, mocks) cho phương thức.

Các framework .NET hiện đại dùng cơ chế này rất nhiều cho event, code mẫu, kế thừa config, thậm chí cả serialize object (ví dụ qua virtual property).

5. Từ khóa new khi ẩn phương thức

Ta đã biết để ghi đè phương thức thì cần cặp virtual/override. Nhưng trong C# còn có một modifier nữa liên quan đến phương thức trong kế thừa — đó là new.

Dùng new để làm gì?

new dùng khi trong class con bạn khai báo phương thức cùng chữ ký với class cha, NHƯNG không muốn ghi đè virtual method, mà chỉ ẩn (che giấu) phương thức cha thôi.

  • Đây không phải ghi đè, mà là ẩn.
  • Phương thức này được gọi theo kiểu biến, không phải kiểu thực tế của object (không có dynamic polymorphism!).
  • Compiler sẽ cảnh báo nếu bạn "vô tình" ẩn phương thức mà không dùng new.

Ví dụ: khác biệt giữa override và new


public class Animal
{
    public virtual  void MakeSound()
    {
        Console.WriteLine("Động vật phát ra một âm thanh nào đó...");
    }
}

public class Dog : Animal
{
    // Ẩn phương thức của class cha (KHÔNG ghi đè)
    public new void MakeSound()
    {
        Console.WriteLine("Đây không phải override! Chỉ là phương thức của chó.");
    }
}
        

Giờ xem hành vi nè:

Animal a = new Dog();
Dog d = new Dog();

a.MakeSound(); // "Động vật phát ra một âm thanh nào đó..."
d.MakeSound(); // "Đây không phải override! Chỉ là phương thức của chó."
  • Nếu biến kiểu Dog — sẽ gọi phương thức của Dog.
  • Nếu biến kiểu Animal, dù chứa Dog — vẫn gọi phương thức Animal!

6. Phản hồi và lưu ý khi hiện thực

Khi mới học lập trình, rất hay gặp trường hợp tưởng đã "ghi đè" mà phương thức vẫn chạy kiểu cũ. Thường lý do đơn giản: class cha không có virtual, hoặc ở class con lại khai báo với new thay vì override. Trường hợp thứ hai rất dễ nhầm — nếu gọi phương thức qua biến kiểu cha, sẽ gọi phiên bản cha chứ không phải bản ghi đè. Nên luôn chú ý dùng đúng từ khóa nha.

Ngoài lỗi cú pháp, đôi khi newbie còn thử đổi kiểu trả về khi ghi đè. Ví dụ, hàm cha trả về object, còn class con lại trả về string. Không được đâu: chữ ký phương thức phải giống hệt nhau.

Bảng so sánh: override vs new

Đặc điểm override new
Cơ chế Ghi đè virtual method Ẩn phương thức của class cha
Late binding Có — hoạt động qua dynamic polymorphism Không — theo kiểu biến
Yêu cầu ở base... virtual, abstract hoặc đã override Không
Nên dùng khi nào Gần như luôn luôn Chỉ khi thật sự cần thiết

7. Ghi đè phương thức với chuỗi kế thừa

Câu chuyện ghi đè càng thú vị hơn khi có chuỗi kế thừa dài:


public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Động vật làm gì đó...");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Gâu-gâu!");
    }
}

public class Labrador : Dog
{
    public override void MakeSound()
    {
        Console.WriteLine("Tôi là labrador: gâu-gâu!");
    }
}
        

Nếu viết như này thì sao:

Animal pet = new Labrador();
pet.MakeSound(); //   => "Tôi là labrador: gâu-gâu!"

C# luôn chọn hiện thực virtual method "sâu nhất" trên object thực tế.

8. Lỗi phổ biến khi ghi đè phương thức

Đời không như mơ, sinh viên (và cả dev kỳ cựu!) vẫn hay mắc lỗi. Cùng học cách tránh mấy "cái bẫy" phổ biến nhé:

1. Quên virtual ở class cha


public class Animal
{
    public void MakeSound() { ... } // Không có 'virtual'
}

public class Dog : Animal
{
    // Lỗi compile! Không thể ghi đè.
    public override void MakeSound()
    {
        Console.WriteLine("Gâu!");
    }
}
        

C# sẽ báo ngay: 'Dog.MakeSound()': không tìm thấy phương thức phù hợp để override

2. Chữ ký không khớp

Đảm bảo tên phương thức, kiểu trả về và tham số giống hệt nhau:


public class Animal
{
    public virtual void MakeSound() { ... }
}

public class Dog : Animal
{
    // Lỗi: chữ ký khác (ví dụ, thêm tham số)
    public override void MakeSound(string sound)
    {
        Console.WriteLine(sound);
    }
}
        

3. Đừng dùng new thay cho override nếu không cần thiết

Từ khóa new cho phép ẩn phương thức cha, nhưng nó không phải ghi đè và không hoạt động qua dynamic polymorphism. Đây là cơ chế khác, thường nên tránh trừ khi thật sự cần.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Sử dụng phương thức cơ sở khi override
Sử dụng phương thức cơ sở khi override
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Làm việc với chuỗi kế thừa
Làm việc với chuỗi kế thừa
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION