24/05/2018, 14:42

Nạp chồng toán tử

Mục tiêu thiết kế của C# là kiểu người dùng định nghĩa (lớp) phải được đối xử như các kiểu định sẵn. Ví dụ, chúng ta muốn định nghĩa lớp phân số (Fraction) thì các chức năng như cộng, trừ, nhân, … phân số là điều tất yếu phải có. Để làm được việc đó ...

Mục tiêu thiết kế của C# là kiểu người dùng định nghĩa (lớp) phải được đối xử như các kiểu định sẵn. Ví dụ, chúng ta muốn định nghĩa lớp phân số (Fraction) thì các chức năng như cộng, trừ, nhân, … phân số là điều tất yếu phải có. Để làm được việc đó ta định nghĩa các phương thức: cộng, nhân, … khi đó, ta phải viết là:

Phân_số tổng = số_thứ_nhất.cộng(số_thứ_hai);
    

Cách này hơi gượng ép và không thể hiện hết ý nghĩa. Điểu ta muốn là viết thành:

Phân_số tổng = số_thứ_nhất + số_thứ_hai;
    

để làm được điều này ta dùng từ khoá operator để thể hiện.

Trong C#, các toán tử là các phương thức tĩnh, kết quả trả về của nó là giá trị biểu diễn kết quả của một phép toán và các tham số là các toán hạng. Khi ta tạo một toán tử cho một lớp ta nói là ta nạp chồng toán tử, nạp chồng toán tử cũng giống như bất kỳ việc nạp chồng các phương thức nào khác. Ví dụ nạp chồng toán tử cộng (+) ta viết như sau:

public static Fraction operator+ (Fraction lhs, Fraction rhs)

Nó chuyển tham số lhs về phía trái toán tử và rhs về phía phải của toán tử.

Cú pháp C# cho phép nạp chồng toán tử thông qua việc dùng từ khoá operator.

C# cung cấp khả năng nạp chồng toán tử cho lớp của ta, nói đúng ra là trong Common Language Specification (CLS). Những ngôn ngữ khác như VB.Net có thể không hổ trợ nạp chồng toán tử, do đó, điều quan trọng là ta cũng cung cấp các phương thức hổ trợ kèm theo các toán tử để có thể thực hiện được ở các môi trường khác. Do đó, khi ta nạp chồng toán tử cộng (+) thì ta cũng nên cung cấp thêm phương thức add() với cùng ý nghĩa.

Các toán tử được nạp chồng có thể giúp cho đoạn mã nguồn của ta dễ nhìn hơn, dễ quản lý và trong sáng hơn. Tuy nhiên nếu ta quá lạm dụng đưa vào các toán tử quá mới hay quá riêng sẽ làm cho chương trình khó sử dụng các toán tử này mà đôi khi còn có các nhầm lẩn vô vị nữa.

Các toán tử khá phổ biến là toán tử (

==
) so sánh bằng giữ hai đối tượng, (
!=
) so sánh không bằng, (
<
) so sánh nhỏ hơn, (
>
) so sánh lớn hơn, (
<=
,
>=
) tương ứng nhỏ hơn hay bằng và lớn hơn hay bằng là các toán tử phải có cặp toán hạng hay gọi là các toán tử hai ngôi.

Nếu ta nạp chồng toán tử so sánh bằng (

==
), ta cũng nên cung cấp phương thức ảo
Equals()
bởi object và hướng chức năng này đến toán tử bằng. Điều này cho phép lớp của ta đa hình và cung cấp khả năng hữu ích cho các ngôn ngữ .Net khác. Phương thức
Equals()
được khai báo như sau:
public override bool Equals(object o)

Bằng cách nạp chồng phương thức này, ta cho phép lớp Fraction đa hình với tất cả các đối tượng khác. Nội dung của

Equals()
ta cần phải đảm bảo rằng có sự so sánh với đối tượng Fraction khác. Ta viết như sau:
public override bool Equals(object o)
{
if (! (o is Fraction) )
{
return false;
}
return this == (Fraction) o;
}

Toán tử is được dùng để kiểm tra kiểu đang chạy có phù hợp với toán hạng yêu cầu không. Do đó, o is Fraction là đúng nếu o có kiểu là Fraction.

Trong C# cũng như C++ hay Java, khi ta chuyển từ kiểu thấp hơn (kích thước nhỏ) lên kiểu cao hơn (kích thước lớn) thì việc chuyển đổi này luôn thành công nhưng khi chuyển từ kiểu cao xuống kiểu thấp có thể ta sẽ mất thông tin. Ví dụ ta chuyển từ int thành long luôn luôn thành công nhưng khi chuyển ngược lại từ long thành int thì có thể tràn số không như ý của ta. Do đó khi chuyển từ kiểu cao xuống thấp ta phải chuyển tường minh.

Cũng vậy muốn chuyển từ int thành kiểu Fraction luôn thành công, ta dùng từ khoá implicit để biểu thị toán tử kiểu này. Nhưng khi chuyển từ kiểu Fraction có thể sẽ mất thông tin do vậy ta dùng từ khoá explicit để biểu thị toán tử chuyển đổi tưởng minh.

using System;
public class Fraction
    {
    public Fraction(int numerator, int denominator)
    {
    Console.WriteLine("In Fraction Constructor(int, int)");
    this.numerator=numerator;
    this.denominator=denominator;
    }
    public Fraction(int wholeNumber)
    {
    Console.WriteLine("In Fraction Constructor(int)");
    numerator = wholeNumber;
    denominator = 1;
    }
    public static implicit operator Fraction(int theInt)
    {
    System.Console.WriteLine("In implicit conversion to Fraction");
    return new Fraction(theInt);
    }
    public static explicit operator int(Fraction theFraction)
    {
    System.Console.WriteLine("In explicit conversion to int");
    return theFraction.numerator / theFraction.denominator;
    }
    public static bool operator==(Fraction lhs, Fraction rhs)
    {
    Console.WriteLine("In operator ==");
    if (lhs.denominator == rhs.denominator &&
    lhs.numerator == rhs.numerator)
    {
    return true;
    }
    // code here to handle unlike fractions return false;
    }
    public static bool operator !=(Fraction lhs, Fraction rhs)
    {
    Console.WriteLine("In operator !=");
    return !(lhs==rhs);
    }
    public override bool Equals(object o)
    {
    Console.WriteLine("In method Equals");
    if (! (o is Fraction) )
    {
    return false;
    }
    return this == (Fraction) o;
    }
    public static Fraction operator+(Fraction lhs, Fraction rhs)
    {
    Console.WriteLine("In operator+");
    if (lhs.denominator == rhs.denominator)
    {
    return new Fraction(lhs.numerator+rhs.numerator, lhs.denominator);
    }
    // simplistic solution for unlike fractions
    // 1/2 + 3/4 == (1*4) + (3*2) / (2*4) == 10/8
    int firstProduct = lhs.numerator * rhs.denominator;
    int secondProduct = rhs.numerator * lhs.denominator;
    return new Fraction(
    firstProduct + secondProduct,
    lhs.denominator * rhs.denominator
    );
    }
    public override string ToString( )
    {
    String s = numerator.ToString( ) + "/" +
    denominator.ToString( );
    return s;
    }
    private int numerator;
    private int denominator;
    }
    public class Tester
    {
    static void Main( )
    {
    //implicit conversion to Fraction
    Fraction f1 = new Fraction(3);
    Console.WriteLine("f1: {0}", f1.ToString( )); Fraction f2 = new Fraction(2,4); Console.WriteLine("f2: {0}", f2.ToString( )); Fraction f3 = f1 + f2;
    Console.WriteLine("f1 + f2 = f3: {0}", f3.ToString( )); Fraction f4 = f3 + 5;
    Console.WriteLine("f3 + 5 = f4: {0}", f4.ToString( ));
    Fraction f5 = new Fraction(2,4);
    if (f5 == f2)
    {
    Console.WriteLine("F5: {0} == F2: {1}", f5.ToString( ), f2.ToString( ));
    }
    int k = (int)f4; //explicit conversion to int
    Console.WriteLine("int: F5 = {0}", k.ToString());
    }
    }
    
    
0