Generic

😢 ปัญหา

ในบางครั้งที่เราเขียนโค้ด เราก็อาจจะไม่รู้ก็ได้ว่าเจ้าตัวแปรตัวนี้มันควรจะมี Data type เป็นอะไรดี เพราะมันขึ้นอยู่กับว่าคนที่เรียกใช้มันจะส่งอะไรมาให้นั่นเอง

ตัวอย่าง: เราอยากมีคลาสที่เอาไว้เก็บข้อมูลอะไรก็ได้ไว้ 1 ตัว ซึ่งตอนแรกอาจจะเก็บค่าตัวเลขไว้ ก็จะได้โค้ดออกมาแบบนี้

public class SimpleStorage
{
public int Value;
}

คำถามคือ แล้วถ้าเราอยากให้คลาสนั้นมันเก็บ double เข้าไปบ้างล่ะ หรืออาจจะเก็บ string บ้างล่ะ เราจะทำยังไงดี ?

คำเตือน เพื่อนๆบางคนอาจจะบอกว่า งั้นก็กำหนดให้มันเป็น object ไปดิ จะเก็บอะไรก็เก็บได้หมดเบย!! คำตอบคือใช่ครับทำแบบนั้นก็ทำงานได้ แต่โค้ดแบบนั้นมันจะทำให้ performance ตกลง เพราะเราต้องไปแปลงของต่างๆให้ไปเป็น object และ ตอนที่เราจะเอาค่ามันกลับมา เราก็ต้องทำการแปลงกลับมาด้วย ซึ่งเราเรียกเรื่องนี้ว่าการทำ Boxing and Unboxing แนะนำว่าอย่าทำ

แนะนำให้อ่าน การทำ Boxing and Unboxing นั้นจริงๆมันเป็นยังไง สามารถอ่านได้จากบทความนี้เลย 💡 Boxing & Unboxing

😄 วิธีแก้ปัญหา

เราสามารถใช้สิ่งที่เรียกว่า Generic มาช่วยแก้ปัญหาที่ว่าได้ โดยหลักการคือมันจะให้คนที่เรียกใช้เป็นคนกำหนดเองว่า data type ที่เขาจะทำงานด้วยคืออะไรนั่นเอง

🔥 ใช้กับตัวแปร

จากโค้ดตัวอย่างด้านบน เราก็สามารถเอา Generic เติมเข้าไปได้เลยเป็นแบบนี้

public class SimpleStorage<T>
{
public T Value;
}

จะเห็นว่าบรรทัดที่ 1 ถูกเพิ่ม <T> ต่อท้ายเข้าไป ซึ่งเจ้านี่แหละคือ generic และส่วนบรรดทัดที่ 2 แทนที่เราจะกำหนดให้เป็น int เราก็เอาเจ้า T จากบรรทัดที่ 1 มาใส่ไว้นั่นเอง อ่านแล้ว งงๆ อยู่ไม่เป็นไร ไปดูต่อกัน

คราวนี้เวลาที่เราจะเรียกใช้คลาสนี้ เราก็จะต้องสร้าง object ของมันพร้อมกับบอกว่าเจ้าคลาสนี้จะทำงานกับ data type อะไรนั่นเอง เช่น อยากทำงานกับ int เราก็จะเขียนโค้ดออกมาเป็นแบบนี้

static void Main(string[] args)
{
SimpleStorage<int> s = new SimpleStorage<int>();
s.Value = 55;
Console.WriteLine(s.Value);
}

จากโค้ดด้านบนจะเห็นว่าตอนที่สร้าง object ของคลาส SimpleStorage เราจะต้องบอกว่า เจ้าคลาสนี้จงทำงานกับ int ซะ (ในบรรทัดที่ 3) ดังนั้นตัวแปรที่ชื่อว่า Value เลยมี data type เป็น int นั่นเอง เลยทำให้มันเก็บค่าตัวเลขลงไปได้ (ในบรรทัดที่ 4)

จากที่ว่ามาเราก็อาจจะสร้าง object ของคลาส SimpleStorage เพื่อให้มันทำงานกับ data type ที่เราอยากทำงานด้วยหลายๆแบบก็ได้ ตามโค้ดนี้เลย

var s1 = new SimpleStorage<int>();
s1.Value = 55;
var s2 = new SimpleStorage<string>();
s2.Value = "Hello world";
var s3 = new SimpleStorage<bool>();
s3.Value = false;

🔥 ใช้กับ Method

นอกจากที่เราจะใช้กับตัวแปรได้แล้ว เรายังสามารถเอา Generic มาใช้กับ method ได้ด้วยนะ ซึ่งถ้าเราใช้กับ method เราสามารถกำหนด generic ไว้กับ method ได้เลย ประมาณนี้

public class Awesome
{
public void ShowIt<T>(T value)
{
Console.WriteLine(value);
}
}

ซึ่งตอนเรียกใช้ก็จะราวๆนี้

int a = 55;
var awe = new Awesome();
awe.ShowIt(a);

และรวมถึงเราจะเอาไปให้มันเป็น return type ก็ได้นะ

public T ShowIt<T>(T value)
{
Console.WriteLine(value);
return value;
}

🔥 Generic แบบหลายตัว

ในบางทีเราก็อยากให้มันทำ generic ไว้หลายๆตัวเราก็สามารถทำได้นะ โดยการคั่น generic แต่ละตัวด้วยคอมม่ายังไงล่ะ

public class Awesome<T,U>
{
public T Value1 { get; set; }
public U Value2 { get; set; }
}

คนเรียกใช้งานก็จะต้องเรียกราวๆนี้

var awe = new Awesome<int, string>();
awe.Value1 = 55;
awe.Value2 = "Hello world";

🤔 ขอตัวอย่างที่เอาไปใช้งานจริงๆหน่อย

มีตรึมเลยที่ Microsoft เขาเขียนไว้เบื้องต้นให้เราแล้ว เช่นเราอยากเก็บข้อมูลในรูปแบบ Stack เราก็สามารถใช้คลาส Stack<T> ได้เลย

var stack = new Stack<int>();
stack.Push(5);
stack.Push(9);
var lastedValue = stack.Pop();
Console.WriteLine(lastedValue); // 9

🎯 บทสรุป

ความสามารถของ generic จะช่วยให้ คนที่เรียกใช้งานเป็นคนกำหนดเองได้ว่า เขาอยากทำงานกับ data type อะไร ซึ่งจะช่วยทำให้โค้ดของเรามีความยืดหยุ่นมากขึ้น และนำกลับมาใช้ใหม่ได้มากขึ้นนั่นเอง

แนะนำให้อ่าน ในการใช้งาน generic นั้นจริงๆมันทำได้เยอะม๊วกกกกก เช่นใช้กับ interface , method, delegate บลาๆ แนะนำให้ไปอ่านเอาต่อนะเพราะไม่งั้นบทความนี้จะยาวเป็นหางว่าวเลย Microsoft document - Generics