CodeGym /Các khóa học /C# SELF /Nạp chồng phương thức (Method Overloading)

Nạp chồng phương thức (Method Overloading)

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

1. Giới thiệu

Ngoài đời nhiều hành động giống như dao đa năng Thụy Sĩ ấy: cùng một lệnh nhưng có thể làm việc với nhiều bộ công cụ khác nhau. Ví dụ, tưởng tượng bạn dùng cây ATM: nếu bạn nhét thẻ vào — ATM hỏi mã PIN; nếu nhập số điện thoại — ATM đợi mã xác nhận từ SMS. Hành động là "kiểm tra người dùng", nhưng cách làm thì khác nhau.

Trong lập trình cũng vậy, nhiều lúc bạn cần thực hiện một thao tác, nhưng dữ liệu lại có thể khác kiểu hoặc khác số lượng tham số. Ví dụ, hàm của bạn cần in lời chào cho người hoặc cho thú, hoặc cộng hai, ba, thậm chí mười số nguyên.

Tất nhiên, bạn có thể đặt tên hàm khác nhau: SumTwo, SumThree, SumArray. Nhưng dev tụi mình lười lắm (người ta bảo lười là động lực phát triển mà). Với lại, code như vậy nhìn chán lắm.

Nạp chồng phương thức

Nạp chồng phương thức — là cách "bắt" một phương thức làm việc với nhiều bộ tham số khác nhau, mà tên hàm vẫn giữ nguyên. Đây là một dạng của đa hình, nhưng không liên quan đến kế thừa nha.

Nạp chồng phương thức nghĩa là bạn có thể tạo trong một class (hoặc struct) nhiều phương thức cùng tên, nhưng khác danh sách tham số (khác kiểu, số lượng và/hoặc thứ tự).

Chữ ký phương thức

Chữ ký của phương thức trong C# là tên hàm cộng với kiểu và thứ tự tham số. Kiểu trả về không nằm trong chữ ký đâu nha! Nhiều bạn hay bị lỗi chỗ này (mình sẽ nói kỹ hơn ở dưới).

2. Nạp chồng thực tế: ví dụ đơn giản

Giờ mình tạo class Greeter để chào user theo nhiều cách: chỉ tên, tên và tuổi, hoặc không tham số luôn.


public class Greeter
{
    // Chào không tham số
    public void Greet()
    {
        Console.WriteLine("Xin chào, thế giới!");
    }

    // Chào với tên
    public void Greet(string name)
    {
        Console.WriteLine($"Xin chào, {name}!");
    }

    // Chào với tên và tuổi
    public void Greet(string name, int age)
    {
        Console.WriteLine($"Xin chào, {name}! Bạn đã {age} tuổi rồi hả? Ghê thật!");
    }
}

Giờ bạn có thể gọi bất kỳ hàm nào, compiler C# sẽ tự chọn đúng phiên bản — dựa vào số lượng và kiểu tham số truyền vào.

var greeter = new Greeter();
greeter.Greet();                // Xin chào, thế giới!
greeter.Greet("Anya");          // Xin chào, Anya!
greeter.Greet("Pyotr", 23);     // Xin chào, Pyotr! Bạn đã 23 tuổi rồi hả? Ghê thật!

3. Phân biệt theo kiểu và số lượng tham số

Nạp chồng hoạt động nếu các phương thức khác nhau về:

  • số lượng tham số,
  • kiểu của ít nhất một tham số,
  • thứ tự kiểu tham số (nhưng cái này phải cẩn thận).

Thử thêm một overload nữa, chỉ nhận tuổi:


public void Greet(int age)
{
    Console.WriteLine($"Từng này tuổi là đỉnh lắm rồi! ({age} tuổi)");
}

Giờ gọi thử:

greeter.Greet(10);          // Từng này tuổi là đỉnh lắm rồi! (10 tuổi)

Nhớ nha: nếu các phương thức chỉ khác kiểu trả về, không nạp chồng được. Ví dụ này sẽ lỗi:


// Lỗi biên dịch!
public int Foo(string s) { ... }
public double Foo(string s) { ... }
Không thể nạp chồng chỉ khác kiểu trả về!

Compiler sẽ báo lỗi: "Đã có phương thức Foo(string) rồi, nghĩ cái gì khó hơn đi!"

4. Nạp chồng và thư viện chuẩn C#

Nạp chồng không chỉ có ở Greet của mình đâu. Mở tài liệu .NET cho Console.WriteLine thử xem:

Chữ ký Mục đích
WriteLine()
In dòng trống
WriteLine(string)
In chuỗi
WriteLine(int)
In số nguyên
WriteLine(double)
In số thực
WriteLine(string, object)
Định dạng chuỗi với một tham số
WriteLine(string, params object[])
Định dạng với nhiều tham số

Tất cả đều là overload của WriteLine. Giờ bạn hiểu vì sao luôn có thể làm thế này:

Console.WriteLine("Chỉ là chuỗi thôi");
Console.WriteLine(123);
Console.WriteLine(2.5);
Console.WriteLine("Tổng: {0}", 42);

Và compiler luôn hiểu đúng ý bạn!

5. Compiler chọn overload nào?

Rất nghiêm túc: nó nhìn vào kiểu và số lượng tham số thực tế truyền vào. Nhìn bảng này cho dễ hiểu:

Gọi Phiên bản nào chạy?
greeter.Greet()
void Greet()
greeter.Greet(75)
void Greet(int)

Nếu bị mơ hồ thì sao?

Đôi khi mọi thứ vượt khỏi tầm kiểm soát. Ví dụ overload mơ hồ — compiler không biết chọn cái nào


public void Print(int a, double b) { ... }
public void Print(double a, int b) { ... }

printer.Print(5, 10); 
// Lỗi: mơ hồ — gọi Print nào đây? (cả hai đều hợp lý)

Compiler sẽ báo lỗi mơ hồ. Trường hợp này nên tránh overload có cùng số tham số và kiểu gần giống nhau, dễ làm compiler rối.

6. params — số lượng tham số linh hoạt

Giả sử bạn muốn tạo hàm nhận số lượng số không xác định. Hãy dùng từ khóa params.


public void SumAll(params int[] numbers)
{
    int sum = 0;
    foreach (int n in numbers)
        sum += n;
    Console.WriteLine($"Tổng: {sum}");
}
Hàm với số lượng tham số linh hoạt ( params)

Giờ bạn có thể gọi:

SumAll(1, 2, 3);           // Tổng: 6
SumAll(10, 20);            // Tổng: 30
SumAll();                  // Tổng: 0

Hàm dùng params có thể kết hợp với overload, nhưng nhớ đừng tạo overload khiến compiler không biết bạn muốn gọi cái nào.

7. Nạp chồng và modifier tham số (ref, out, in)

C# phân biệt phương thức theo modifier tham số (tức là, chữ ký void Foo(int a) khác void Foo(ref int a), và cả hai cùng tồn tại trong một class):

public void SetValue(int a)
{
    a = 42;
}

public void SetValue(ref int a)
{
    a = 100;
}
Nạp chồng theo modifier ref

Gọi không có ref sẽ vào phiên bản đầu, có ref sẽ vào phiên bản sau:

int n = 5;
SetValue(n);     // n vẫn là 5 (giá trị được copy)
SetValue(ref n); // n thành 100

8. Sơ đồ: nạp chồng là gì


      +----------+
      |  MyClass |
      +----------+
           |
           |            (một số phương thức)
    +-----------------------+
    |   void Foo()          |
    |   void Foo(int a)     |
    |   void Foo(string s)  |
    |   void Foo(int a, int b) |
    +-----------------------+
Sơ đồ nạp chồng phương thức trong class

Nếu viết code thì:

// Gọi các phiên bản overload của Foo():
var mc = new MyClass();
mc.Foo();              // void Foo()
mc.Foo(5);             // void Foo(int)
mc.Foo("Hello");       // void Foo(string)
mc.Foo(2, 3);          // void Foo(int, int)

9. Ví dụ: nạp chồng phương thức trong app của mình

Tiếp tục phát triển app học tập, thêm overload vào hệ động vật.


public class Animal
{
    public string Name { get; set; }

    // Phương thức phát ra âm thanh
    public virtual void MakeSound()
    {
        Console.WriteLine("Âm thanh gì đó khó hiểu...");
    }

    // Overload: âm thanh với độ lớn chỉ định
    public void MakeSound(int volume)
    {
        Console.WriteLine($"Động vật phát ra âm thanh với độ lớn {volume} dB.");
    }
}

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

    // Overload: gâu với độ lớn
    public void MakeSound(int volume)
    {
        Console.WriteLine($"Gâu! (độ lớn: {volume} dB)");
    }
}

Thử gọi như này:

Dog rex = new Dog();
rex.MakeSound();           // Gâu!
rex.MakeSound(75);         // Gâu! (độ lớn: 75 dB)

Lưu ý: trong class con (Dog) mình overload MakeSound(int volume), nên giờ nó có cả hai phiên bản: có và không có tham số.

10. Lỗi thường gặp khi nạp chồng phương thức

Lỗi #1: cố nạp chồng chỉ khác kiểu trả về.
Không được đâu — kiểu trả về không nằm trong chữ ký. Nạp chồng phải khác về số lượng hoặc kiểu tham số đầu vào, chứ không phải void hay int.

Lỗi #2: overload mơ hồ làm compiler rối.
Overload cùng số tham số và kiểu gần giống nhau (ví dụ int và double) dễ làm compiler không biết chọn cái nào. Ví dụ: Print(int a, double b) và Print(double a, int b) — gọi Print(1, 1) sẽ lỗi mơ hồ.

Lỗi #3: params bị trùng với overload khác.
Hàm có params có thể bắt luôn cả lệnh gọi dành cho overload khác. Nếu kiểu giống nhau, compiler có thể chọn nhầm hàm bạn không mong muốn.

Lỗi #4: quên rằng ref và out nằm trong chữ ký.
Hàm Do(ref int x) và Do(out int x) là hai overload khác nhau. Nếu không để ý, dễ gọi nhầm phiên bản.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Tạo các phương thức nạp chồng để tính diện tích các hình
Tạo các phương thức nạp chồng để tính diện tích các hình
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Phương thức xuất dữ liệu có định dạng
Phương thức xuất dữ liệu có định dạng
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION