Operator Overloading Cơ Bản Nhất Trong C#

Nạp chồng toán tử là gì? Sử dụng nó như thế nào? Nó có lợi ích gì trong quá trình học và làm việc? Bài viết hôm nay mình sẽ giới thiệu và đưa ra ví dụ cơ bản nhất về nạp chồng toán tử trong C# cho các bạn. Cùng theo dõi nhé.

Operator Overloading trong C# là gì?

Đối với các kiểu dữ liệu về số học, C# hay bất kì ngôn ngữ lập trình nào đều hỗ trợ các toán tử một ngôi, hai ngôi như các phép toán cộng trừ nhân chia cơ bản, phép toán so sánh hay phép toán tăng giảm giá trị,…

Tuy nhiên, trong lập trình hướng đối tượng, các object do người dùng khai báo lại không thể sử dụng các toán tử trên một cách trực tiếp được, khiến các đoạn code trở nên thiếu tường minh và khó hiểu cho người đọc.

Để giải quyết vấn đề trên, C# đã cho phép overload (nạp chồng) các toán tử trên, với các class (lớp) do người dùng tự xây dựng.

Các toán tử có thể overload trong C#

Để hiểu rõ hơn về cách hoạt động của Operator overloading trong C#, chúng ta cùng đi tới một ví dụ điển hình sau.         

Xây dựng class phân số và các phép toán phân số

Yêu cầu đặt ra là chúng ta phải xây dựng một class về phân số, sử dụng Operator overloading như đã giới thiệu ở trên để thực hiện các phép toán cộng trừ nhân chia phân số đơn giản.

class Phanso
{
    private int tuso, mauso;
    public Phanso()
    {
        this.mauso = 1;
    }
    public int Tuso
    {
        get
        {
            return this.tuso;
        }
        set
        {
            this.tuso = value;
        }
    }
    public int Mauso
    {
        get
        {
            return this.mauso;
        }
        set
        {
            try
            {
                if (value == 0)
                {
                    Exception MauBangKhong = new Exception("Mau phai khac 0");
                    throw MauBangKhong;
                }
                else
                {
                    this.mauso = value;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw e;
            }
        }
    }
    public void Rutgon()                   
    {
        int a = this.Tuso;
        int b = this.Mauso;
        int ucln;
        if (a < 0)
        {
            a = a * (-1);
        }
        if (b < 0)
        {
            b = b * (-1);
        }
        if (a == 0)
        {
            ucln = a + b;
        }
        while (a != b)
        {
            if (a > b)
            {
                a -= b; 
            }
            else
            {
                b -= a;
            }
        }
        ucln = a;
        this.Tuso /= ucln;
        this.Mauso /= ucln;
    }
    public static Phanso operator +(Phanso a, Phanso b)
    {
        Phanso c = new Phanso();
        c.Mauso = a.Mauso * b.Mauso;
        c.Tuso = a.Tuso * b.Mauso + b.Tuso * a.Mauso;
        c.Rutgon();
        return c;
    }
    public static Phanso operator -(Phanso a, Phanso b)
    {
        Phanso c = new Phanso();
        c.Mauso = a.Mauso * b.Mauso;
        c.Tuso = a.Tuso * b.Mauso - b.Tuso * a.Mauso;
        c.Rutgon();
        return c;
    }
    public static Phanso operator *(Phanso a, Phanso b)
    {
        Phanso c = new Phanso();
        c.Mauso = a.Mauso * b.Mauso;
        c.Tuso = a.Tuso * b.Tuso;
        c.Rutgon();
        return c;
    }
    public static Phanso operator /(Phanso a, Phanso b)
    {
        Phanso c = new Phanso();
        try
        {
            if (b.Tuso == 0)
            {
                Exception ThuongBangKhong = new Exception("Thuong phai khac 0");
                throw ThuongBangKhong;
            }
            else
            {
                c.Tuso = a.Tuso * b.Mauso;
                c.Mauso = a.Mauso * b.Tuso;
                c.Rutgon();
                return c;
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw e;
        }
    }
    public double Giatri()
    {
        return Convert.ToDouble(this.Tuso) / Convert.ToDouble(this.Mauso);
    }
    public override string ToString()
    {
        return Convert.ToString(this.Tuso) + "/" + Convert.ToString(this.Mauso);
    }
}

Giải thích:

Trong đoạn code trên, mình khai báo 1 class Phanso thông thường với 2 thuộc tính private là tuso (Tử số) và mauso (Mẫu số) với các phương thức get set cơ bản. Một vài hàm hỗ trợ tính toán như hàm Rutgon() để rút gọn phân số, hàm Giatri() để trả về giá trị của phân số dưới dạng double .Operator Overloading ở đoạn code trên được thể hiện qua 6 toán tử “+, -, *, /” và “>, <“. Cú pháp tổng quát sẽ là:

public static <Kiểu dữ liệu trả về> operator <toán tử> (Các tham số truyền vào)
{
     // Code
     return ...
}

Ví dụ với toán tử cộng hai phân số “+” như sau:

public static Phanso operator +(Phanso a, Phanso b)
{
      Phanso c = new Phanso();
      c.Mauso = a.Mauso * b.Mauso;
      c.Tuso = a.Tuso * b.Tauso + b.Tuso * a.Mauso;
      c.Rutgon();
      return c;
}

Trong C#, mình còn có thể viết gọn đoạn code trên lại như này:

public static Phanso operator +(Phanso a, Phanso b)
  => new Phanso { Tuso = a.Tuso * b.Mauso + b.Tuso * a.Mauso, Mauso = a.Mauso * b.Mauso };

Tương tự như phép cộng, thì mình có thể xây dựng được các phép trừ, nhân, chia hay so sánh 2 phân số rồi nha.

Công việc xây dựng class Phanso đã xong, giờ ta cùng quay xuống hàm Main để xem đoạn code kia chạy như nào nhé. Ở hàm Main ta thử một ví dụ sau đây:

static void Main(string[] args)
{
    Phanso a = new Phanso { Tuso = 5, Mauso = 4 };
    Phanso b = new Phanso { Tuso = 3, Mauso = 7 };
    Phanso c = new Phanso { Tuso = -4, Mauso = 5 };
    Console.WriteLine(a + b + c);
    Console.WriteLine(a * b * c);
    Console.ReadKey();
}

Kết quả thu được sẽ là:

123/140
-3/7

Class phân số đơn giản đã được mình xây dựng xong, với bốn phép toán cộng trừ nhân chia thông thường. Tất nhiên, các bạn có thể hoàn thiện thêm bằng cách thực hiện overloading các toán tử khác như toán tử so sánh, các toán tử một ngôi, thêm vào đó các hàm khác như hàm lũy thừa, phân số nghịch đảo, … để phục vụ việc tính toán trên phân số.

Kết luận

Như vậy, qua ví dụ trên, chúng ta có thể hiểu được phần nào cách hoạt động của Operator Overloading trong C#, cũng như những lợi ích mà nó đem lại. Operator overloading giúp cho code của người lập trình trở nên tường minh hơn, dễ đọc, dễ bảo trì, do vậy các bạn nên học và tập sử dụng chúng một cách thường xuyên hơn. 

Qua bài viết này, mình hy vọng đem đến cho các bạn một cái nhìn tổng quan nhất về Operator Overloading trong C#, mọi sai sót cũng như thắc mắc về bài viết của mình mong được các bạn để lại comment phía bên dưới. Hẹn gặp lại các bạn trong những bài viết tiếp theo.