/ 365วันแห่งโปรแกรม

[365 วันแห่งโปรแกรม #day41] Operator Overloading

วันที่สี่สิบเอ็ดของ ‪#‎365วันแห่งโปรแกรม ได้เวลาเปลี่ยนเรื่องใหม่ครับ แต่ก็ยังเกี่ยวกับ polymorphism เหมือนเดิม ซึ่งเรื่องที่เราจะคุยวันนี้ก็คือ Operator Overloading


โปรแกรมเมอร์ทุกท่านนะจะคุ้นเคยกับการ Overload Function กันอยู่แล้วใช่ไหมครับ? วันนี้เราจะมาลอง Overload อย่างอื่นที่ไม่ใช่ Functionซึ่งนั่นก็คือ Operator ครับ

Operator Overloading คืออะไร?

Operator Overloading คือการกำหนดความสามารถให้ Operator เมื่อถูกใช้งานกับ Type ต่างๆ ซึ่งจริงๆ แล้วก็ไม่ได้ต่างจากการทำ Function Overloading เลย เราสามารถมองว่า Operator ก็คือ Function เหมือนกัน แต่มีความพิเศษตรงที่รูปแบบการใช้งานที่ต่างออกไปนั่นเอง

ข้อดีของ Operator Overloading คืออะไร?

ผมว่าทุกคนน่าจะเคยหงุดหงิดเกี่ยวกับการใช้ Method/Function ทำอะไรบางอย่าง ที่มันควรจะง่ายกว่านั้น เช่น การเปรียบเทียบเวลา ทำไมเราต้องใช้ Method/Function ล่ะ ทำไมไม่ใช้แค่เครื่องหมายลบ (-) เลย ครับ จริงๆ แล้ว Operator Overloading เป็นแค่ Syntactic sugar ซึ่งทำให้เราเขียนโค้ง่ายขึ้น อ่านโค้ดง่ายขึ้น แค่นั้นเลยครับ

ทำไมไม่ค่อยเห็นใครทำ Operator Overloading เลย

ไม่ใช่ทุกภาษาโปรแกรมที่ยอมให้เราทำ Operator Overloading ได้ และภาษาโปรแกรมที่ทำได้ก็ยังแบ่งเป็นภาษาที่ยอมให้กำหนด Operator ขึ้นมาใหม่ กับภาษาที่ยอมให้ใช้เฉพาะที่กำหนดไว้แล้ว (จำกัด) อีกด้วย นั่นก็เป็นเหตุผลหนึ่งที่การทำ Operator Overloading ไม่เป็นที่แพร่หลายสักเท่าไหร่

อีกเรื่องหนึ่งคือการทำ Operator Overloading นั้นทำให้สูญเสียคุณสมบัติของ operator เดิมไป เช่นในจาวาเราใช้เครื่องหมายเท่ากับ (==) ในการเปรียบเทียบ Address ของ Object แต่ถ้าเรา Overload ไปแล้วมันก็จะทำหน้าที่อื่นแทน

ในความเป็นจริงภาษาจาวาไม่ยอมให้ทำ Operator Overloading

ไม่ว่าอย่างไรก็ตาม ไม่ว่าจะใช้หรือไม่ใช้ หน้าที่ของโปรแกรมเมอร์ที่ดีก็คือเรียนรู้ครับ รู้ไว้ให้มากๆ แค่นั้นเลย

วิธีการทำ Operator Overloading เป็นอย่างไร?

จริงๆ แล้วไม่ต่างจาก Method/Function Overloading ครับ ก็คือสร้าง Function ขึ้นมาตัวนึงตามที่ Syntax ของภาษานั้นๆ กำหนด เด๊่ยววันนี้เราจะมาลองดูการทำ Operator Overloading ใน C# กันครับ

โจทย์ของเราในวันนี้คือการทำ Operator Overloading ให้กับเครื่องหมาย + ของ Set (ซ็ตแบบในคณิตศาสตร์) เพื่อใช้สำหรับรวมเซ็ต 2 ตัวเข้าด้วยกัน

เริ่มจากสร้างคลาส CSet ง่ายๆ ที่ Derived มาจาก ICollection ครับ (ข้างในเก็บข้อมูลเป็น List ไปก่อนละกันครับ ง่ายดี)

class CSet<T> : ICollection<T>
{
    List<T> items = new List<T>();

    public void Add(T item)
    {
        if (!items.Contains(item))
        {
            items.Add(item);
        }
    }

    public void Clear()
    {
        items.Clear();
    }

    public bool Contains(T item)
    {
        return items.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        items.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return items.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(T item)
    {
        return items.Remove(item);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return items.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return items.GetEnumerator();
    }
}

จะเห็นว่าข้างในจริงๆ แล้วเป็น List หมดเลย เราแค่สร้างคลาสมาครอบแค่นั้น แต่ตรง Add() เราจะต้องเช็คก่อนว่ามีสมาชิกนี้อยู่หรือยัง เพราะคุณสมบัติของ Set คือเก็บค่าที่ไม่ซ้ำกัน

ต่อมาเราจะทำการ Overload Operator + ครับ

public static CSet<T> operator +(CSet<T> c1, CSet<T> c2)
{
    CSet<T> newSet = new CSet<T>();
        
    foreach(var item in c1){
        newSet.Add(item);
    }

    foreach (var item in c2)
    {
        newSet.Add(item);
    }

    return newSet;
}

จากโค้ดด้านบนเป็นการทำ Operator Overloading ครับ ซึ่งใน C# จะใช้วิธีสร้าง static method ขึ้นมา โดยใส่ Return type ตามด้วย คำว่า operator เว้นวรรค ใส่เครื่องหมายที่ต้องการ Overload ครับ (ใช้วิธีเดียวกันในการ Overload Operator แบบ Infix, Prefix, และ Suffix) ข้างใน Method ของเราก็ง่ายๆ เลยครับ สร้าง CSet ขึ้นมาใหม่ตึวนึง แล้วก็ Add ข้อมูลของสองตัวเก่าลงไปเลย (ใน Method Add นั้นเราเช็คอยู่แล้วว่าซ้ำหรือไม่ ดังนั้นจึงไม่ต้องเช็คอีก)

ต่อมาลองเอาคลาสที่สร้างมาใช้ดู

CSet<int> set1 = new CSet<int> { 1, 2, 3, 4, 5 };
CSet<int> set2 = new CSet<int> { 2, 4, 6, 8, 10 };

CSet<int> set3 = set1 + set2;

foreach (var item in set3)
{
    Console.WriteLine(item);
}

จากโค้ดนั้นเป็นการส้ราง Set ขึ้นมาสองตัว ตัวแรกมีสมากชิกเป็น 1, 2, 3, 4, 5 และตัวที่สองมีสมาชิกเป็น 2, 4, 6, 8, 10 หลังจากนั้นสร้าง Set อีกตัว ซึ่งเป็นผลบวกของ 2 ตัวแรก ถ้าเราลองเรา 2 เซ็ตแรกมาบวกกันดู จะได้ 1, 2, 3, 4, 5, 6, 8, 10 งั้นเรามาดูผลลัพธ์กันดีกว่าว่าโปรแกรมทำงานถูกต้องไหม

===output===
1
2
3
4
5
6
8
10

จะเห็นว่าถูกต้อง นั่นแสดงว่าการทำ Operator Overloading ของเรานั้นใช้ได้ครับ

#‎day41 #365วันแห่งโปรแกรม ‪#‎โครงการ365วันแห่ง‬...