ข้อดีของภาษา C# เมื่อเทียบกับภาษาอื่น ๆ ตอนที่ 9

ข้อดีของภาษา C# เมื่อเทียบกับภาษาอื่น ๆ ตอนที่ 9
การเรียกใช้เมธอดเสริม
เมธอดเสริม (Extension Method) แปลกกว่าเมธอดสมาชิกอื่น ๆ ที่เวลานิยามเราต้องนิยามมันแบบสแตติกเมธอด (static method เมธอดที่เป็นสมาชิกของคลาส ไม่ใช่ของออพเจ็กต์) แต่เวลาเรียกใช้งานเรากลับต้องเรียกใช้งานแบบอินสแตนซ์เมธอด (instance method เมธอดที่เป็นสมาชิกของออพเจ็กต์ ไม่ใช่ของคลาส) พารามิเตอร์ตัวแรกของเมธอดเป็นตัวกำหนดว่าเมธอดนั้นทำงานกับอะไร ในรูปที่ 1 บรรทัดที่ 9 จะเห็นว่ามีคำสั่ง (โมดิไฟเออร์) this ปรากฏอยู่หน้าพารามิเตอร์ คำสั่ง this ทำหน้าที่กำหนดว่าเมธอดนี้ทำงานกับออพเจ็กต์ (ไม่ใช่กับคลาส)

เราจะเรียกใช้เมธอดเสริมใด ๆ ได้ก็ต่อเมื่อนำเข้าเนมสเปสด้วยคำสั่ง using ตามด้วยเนมสเปสนั้น รูปที่ 1 คือตัวอย่างโค้ดเพื่อใส่เมธอดเสริมให้แก่ String ที่เป็นคลาส System.String โดยใช้ชื่อว่าเมธอดว่า WordCount ซึ่งเป็นเมธอดที่อยู่ภายในสแตติกคลาสธรรมดาที่ไม่ได้ซ้อนอยู่ภายในคลาสอื่น บรรทัด 17,18 คือตัวอย่างโค้ดการเรียกใช้เมธอดนี้จะเห็นว่าเหมือนกับ String มีเมธอดใหม่ชื่อ WordCount เพิ่มขึ้น หากเมธอด test ไม่ได้อยู่ภายในเนมสเปสเดียวกันกับเมธอด WordCount ท่านจะต้องใช้คำสั่ง using (บรรทัด 2) เพื่ออ้างถึงเนมสเปสของเมธอด WordCount ด้วยจึงจะเรียกหาได้ แม้ว่าเราจะเรียกใช้งานเมธอดเสริมเหมือนกับอินสแตนซ์เมธอดแต่ตัวแปลภาษาจะสร้างโค้ดภาษากลาง (IL) เป็นการเรียกสแตติกเมธอด ดังนั้นการทำงานจะไม่ผิดหลักวัตถุวิธี และอันที่จริงแล้วเมธอดเสริมจะไม่สามารถเรียกหาตัวแปรไพรเวทของไทป์ที่มันเสริมได้ ทำให้ไม่ผิดหลักการเอนเคปซูเลชัน (ที่เป็นหนึ่งในสามเสาหลักของ OOP)
ในการเขียนโค้ดโดยปรกติเราจะเรียกใช้เมธอดเสริมมากกว่าที่จะสร้างขึ้นเอง การเรียกใช้ทำได้ง่ายเพราะมีซินแท็กซ์เหมือนการเรียกใช้อินสแตนซ์เมธอดตามปรกติ เราจึงไม่จำเป็นต้องรู้อะไรเพิ่มเป็นพิเศษ สิ่งที่ต้องทำมีเพียงแต่ใส่คำสั่ง using เพื่ออ้างถึงเนมสเปสที่มีนิยามเมธอดเสริมนั้น ๆ ยกตัวอย่างเช่นเมื่อต้องการใช้งานเกี่ยวกับคิวรีก็เพียงใช้คำสั่งอย่างบรรทัดที่ 3 (อาจต้องเพิ่มการอ้างอิงไปยังแอสเซมบลีชื่อ System.Core.dll) หลังจากนั้นท่านจะพบว่าเมื่อทำงานกับไทป์ใด ๆ ที่เป็น IEnumerable<T> ตัวแนะนำคำสั่ง (IntelliSense) จะแสดงเมธอดเสริมที่มีทั้งหมดได้ครบ
กฎของการผูกเมธอด
เราอาจสร้างเมธอดเสริมเพื่อเสริมคลาสหรืออินเตอร์เฟสได้ แต่จะใช้ทับเมธอดที่มีอยู่แล้วไม่ได้ ถ้าเรานิยามเมธอดเสริมต่อคลาสหรืออินเตอร์เฟสโดยมีซินเนเจอร์ตรงกันกับเมธอดที่มีอยู่แล้วเราจะเรียกใช้เมธอดเสริมไม่ได้ สาเหตุที่เป็นเช่นนั้นเพราะตอนคอมไพล์ตัวแปลภาษาจะถือว่าเมธอดที่มีอยู่แล้วมีลำดับความสำคัญสูงกว่าเมธอดเสริม ยกตัวอย่างเช่นถ้าไทป์นั้น ๆ มีเมธอด Process(int i) อยู่แล้วเรานิยามเมธอดเสริมชื่อเดียวกันพารามิเตอร์เหมือนกัน ตัวแปลภาษาจะเรียกใช้ตัวเดิมของไทป์เสมอ นั่นคือเมื่อตัวแปลภาษาพบการเรียกใช้เมธอด มันจะตรวจหาเมธอดแบบอินสแตนซ์ก่อน ถ้าไม่พบจึงจะตรวจหาเมธอดเสริม

ตัวอย่างโค้ดต่อไปนี้สาทิตการทำงานของตัวแปลภาษาว่าจะเรียกใช้เมธอดภายในของไทป์หรือเมธอดเสริม คลาส Extensions (รูปที่ 3) มีนิยามของเมธอดเสริมที่สามารถทำงานกับไทป์อะไรก็ได้ที่ล้อตามอินเตอร์เฟส IMyInterface (รูปที่ 2)

ตัวอย่างโค้ดที่นิยามเมธอดเสริม โค้ดบรรทัด 35 จับรวมเนมสเปส DefineIMyInterface โดยใช้คำสั่ง using เพราะต้องการอ้างถึงไทป์นี้ในบรรทัดที่ 41, 47, 56 เมธอด MethodA ในบรรทัดที่ 41 กับเมธอด MethodA ในบรรทัดที่ 47 ถึงแม้ว่าจะมีชื่อเหมือนกันแต่ก็มีซิกเนเจอร์ (ชนิดและการเรียงลำดับของพารามิเตอร์) ไม่ตรงกันจึงถือว่าเป็นเมธอดคนละตัวกัน ส่วนเมธอดเสริม MethodB (รูปที่ 3 บรรทัด 56) จะไม่มีวันถูกเรียกใช้เพราะมันมีชื่อและซินเนเจอร์ตรงกันทุกอย่างกับเมธอดในคลาสทั้งสาม เมื่อตัวแปลภาษาไม่พบเมธอดภายในมันจึงจะเรียกใช้เมธอดเสริม

เนมสเปส ExtensionMethodsDemo1 ที่มีคลาส A B และ C ซึ่งเป็นตัวอย่างนิยามคลาสที่ล้อตามอินเตอร์เฟส IMyInterface โดยคลาส A มีนิยามเมธอด MethodB คลาส B มีนิยามเมธอด MethodB และ มีนิยามเมธอด MethodA ด้วย และคลาส C ก็มีนิยามเมธอด MethodB และ มีนิยามเมธอด MethodA ด้วยเช่นกัน เนื่องจากทุกคลาสมีนิยามเมธอด MethodB ทำให้เมธอดเสริม MethodB (รูปที่ 3 บรรทัด 56) จะไม่มีวันถูกเรียกใช้

เมธอด Main คือส่วนที่เป็นโค้ดตัวอย่างแสดงการเรียกใช้เมธอดเสริม โค้ดบรรทัด 109, 110 เรียกเมธอด MethodA แต่ในคลาส A ไม่มีเมธอดชื่อนี้มีผลให้เมธอดเสริมถูกเรียกใช้ ในขณะที่บรรทัด 114 เรียกเมธอด MethodB ที่มีนิยามไว้แล้วใน A มีผลให้เมธอดเสริมไม่ถูกเรียกใช้งาน

ผลลัพธ์การทำงานของโค้ดตัวอย่างในรูป 2-5
กฎการสร้างเมธอดเสริม
โดยทั่วไปแล้วถ้าไม่จำเป็นจริง ๆ ไม่ควรนิยามเมธอดเสริม ถ้าต้องการเพิ่มเมธอดให้ใช้วิธีสืบคุณสมบัติเป็นคลาสลูกแล้วไปใส่เมธอดเพิ่มเติมในนั้น ในกรณีที่อยากทำเมธอดเสริมเพราะไม่สามารถแก้ไขซอร์สโค้ดของไทป์ ให้ระวังไว้ว่าการเปลี่ยนแปลงโค้ดของไทป์ภายหลังอาจทำให้เมธอดเสริมไม่ทำงาน ในกรณีที่จำเป็นต้องทำเมธอดเสริมมีประเด็นที่ควรระวังสองประการคือ
- เมธอเสริมจะไม่ทำงานหากมีเมธอดเดิมที่เหมือนกันอยู่แล้วภายในไทป์
- เมธอดเสริมจะทำงานได้ต่อเมื่อนำเข้าเนมสเปสที่นิยามเมธอดเสริมด้วยคำสั่ง using
ในกรณีของการสร้างไลบรารี ไม่ควรใช้เมธอดเสริมเพื่อหลีกเลียงการเพิ่มเวอร์ชันของแอสแซมบลี ในกรณีที่ต้องการเพิ่มคุณสมบัติการทำงานของไลบรารีอย่างขนานใหญ่ควรใช้วิธีที่แนะนำไว้ในดอตเน็ตเฟรมเวิร์ก
รีเฟอร์เรนซ์
รีเฟอร์เรนซ์ (Reference) เป็นคุณสมบัติที่มีประโยชน์อีกอย่างหนึ่งในภาษาซีชาร์ปที่หลาย ๆ ภาษาไม่มี การทำรีเฟอร์เรนซ์ในภาษาซีชาร์ปทำได้โดยใช้คำสั่ง ref เราอาจใช้รีเฟอร์เรนซ์ได้ในบริบทต่าง ๆ ดังนี้
- ใช้ในซิกเนเจอร์ของเมธอดและในการเรียกเมธอดเพื่อส่งอาร์กิวเมนต์ไปยังเมธอดโดยวิธีรีเฟอร์เรนซ์
- ใช้ในซิกเนเจอร์ของเมธอดเพื่อส่งค่ากลับไปให้โค้ดที่เรียกเมธอดโดยวิธีรีเฟอร์เรนซ์
- ใช้ร่วมกับสมาชิกของคลาสเพื่อบอกให้รู้ว่าเป็นตัวแปรท้องถิ่นที่เข้าถึงค่าต่าง ๆ โดยวิธีรีเฟอร์เรนซ์
- ใช้ร่วมกับการประกาศสตรักเจอร์เพื่อบังคับให้อินแสตนซ์ของมันถูกเก็บไว้ในสแต็ก
การส่งอาร์กิวเมนต์แบบรีเฟอร์เรนซ์
เราอาจใส่คำสั่ง ref ไว้หน้ารายการอาร์กิวเมนต์ของเมธอดเพื่อระบุว่าอาร์กิวเมนต์นั้น ๆ จะทำงานโดยใช้วิธีรีเฟอร์เรนซ์ (คือไม่ใช่โดยวิธีส่งค่าตามปรกติ) เมื่อใส่ ref แล้วผลที่เกิดขึ้นคือการเปลี่ยนแปลงใด ๆ ต่อค่าของตัวแปรในเมธอดที่ถูกเรียกจะส่งผลไปยังตัวแปรในเมธอดที่เรียกด้วย ยกตัวอย่างเช่นเราส่งตัวแปรท้องถิ่นหรืออาร์เรย์ไปเป็นแบบ ref เมื่อมีการเปลี่ยนแปลงการอ้างถึงออพเจ็กต์ภายในเมธอดที่ถูกเรียก ตัวแปรท้องถิ่นหรืออาร์เรย์ในเมธอดที่เป็นผู้เรียกก็จะเปลี่ยนไปด้วย
โปรดอย่าสับสนระหว่างการส่งค่าแบบรีเฟอร์เรนซ์ด้วยคำสั่ง ref กับแนวคิดเรื่อง “ตัวแปรแบบรีเฟอร์เรนซ์” ในภาษาซีชาร์ป เพราะเป็นคนละเรื่องกัน ภาษาซีชาร์ปมีตัวแปรสองแบบคือ “ตัวแปรเก็บค่า” (Value Type: VT) กับ “ตัวแปรเก็บค่าอ้างอิง” (Reference Type: RT) เราสามารถส่งค่าแบบรีเฟอร์เรนซ์ด้วยคำสั่ง ref กับตัวแปรได้ทั้งสองแบบ

คือตัวอย่างการใส่คำสั่ง ref ไว้หน้ารายการอาร์กิวเมนต์ของเมธอดเพื่อระบุว่าอาร์กิวเมนต์นั้น ๆ จะทำงานโดยใช้วิธีรีเฟอร์เรนซ์ ท่านจะต้องใส่คำสั่ง ref ไว้สองแห่ง คือใส่หน้าอาร์กิวเมนต์ที่โค้ดส่วนเรียกเมธอด (บรรทัด 14) และใส่ไว้หน้าพารามิเตอร์ของเมธอดที่จะถูกเรียก (บรรทัด 19)