มีอะไรใหม่ใน .NET Core 2 และ C# 7 : Pattern Matching ด้วยคำสั่ง Switch

ในภาษา C# เวอร์ชัน 7.0 การตรวจสอบเพื่อการจับคู่รูปแบบ (Pattern Matching: PM) ด้วยคำสั่ง if และ switch ได้รับการพัฒนาให้ดียิ่งขึ้น ยืดหยุ่นกว่าเดิม เขียนโค้ดได้สะดวกขึ้น แต่เดิมการทำ PM ด้วยหลักวัตถุวิธีเราจะสร้างคลาสฐานเป็นแบบ “abstract” จากนั้นจะใช้กรรมวิธีสืบคุณสมบัติเป็นคลาสลูกหลาย ๆ แบบตามต้องการ ส่วนเก็บข้อมูลและโค้ดเพื่อจัดการข้อมูลจะถูกผนึกไว้เป็นหน่วยเดียวกัน ซึ่งเป็นการ “เชื่อมแน่น” (tight coupling) ในกรณีที่เราต้องการการ “เชื่อมหลวม” (loose coupling) เราจะแยก ส่วนเก็บข้อมูลและโค้ดเพื่อจัดการข้อมูลออกจากกัน จากนั้นทำ PM ด้วยคำสั่ง if และ switch ซึ่งหากมีรูปแบบจำนวนวนมาก โค้ดจะยืดยาวเยิ่นเย้อ
Switch Statement with Pattern Matching
ทักษะ (ระบุได้หลายทักษะ)

การตรวจสอบเพื่อการจับคู่รูปแบบ (Pattern Matching: PM) 

ในภาษา C#  เวอร์ชัน 7.0 การตรวจสอบเพื่อการจับคู่รูปแบบ (Pattern Matching: PM) ด้วยคำสั่ง if และ switch ได้รับการพัฒนาให้ดียิ่งขึ้น ยืดหยุ่นกว่าเดิม เขียนโค้ดได้สะดวกขึ้น

แต่เดิมการทำ PM ด้วยหลักวัตถุวิธีเราจะสร้างคลาสฐานเป็นแบบ “abstract” จากนั้นจะใช้กรรมวิธีสืบคุณสมบัติเป็นคลาสลูกหลาย ๆ แบบตามต้องการ
ส่วนเก็บข้อมูลและโค้ดเพื่อจัดการข้อมูลจะถูกผนึกไว้เป็นหน่วยเดียวกัน ซึ่งเป็นการ “เชื่อมแน่น” (tight coupling)

ในกรณีที่เราต้องการการ “เชื่อมหลวม” (loose coupling) เราจะแยก ส่วนเก็บข้อมูลและโค้ดเพื่อจัดการข้อมูลออกจากกัน
จากนั้นทำ PM ด้วยคำสั่ง if และ switch ซึ่งหากมีรูปแบบจำนวนวนมาก โค้ดจะยืดยาวเยิ่นเย้อ


รูปที่ 1
นิยามคลาส Square  และ นิยามคลาส Circle
ยกตัวอย่างเช่นหากเราต้องการสร้าง object ที่มีรูปทรงเรขาคณิต อย่าง สี่เหลี่ยม วงกลม สี่เหลี่ยมผืนผ้า และสามเหลี่ยม
เราจะนิยามคลาสแบบ abstract เพื่อใช้ทำหน้าที่เป็นเบสคลาสที่เป็นรูปทรงกลาง
จากนั้นนิยามคลาสลูกให้สืบคุณสมบัติจาก base class เป็นรูปทรงต่าง ๆ หนึ่งคลาสต่อหนึ่งรูปทรง
และใส่นิยาม method จัดการกับข้อมูลของรูปทรงที่แตกต่างกันนั้น ๆ ในแต่ละคลาส 

แต่ถ้าเราต้องการแยกโค้ดที่จัดการข้อมูลออกจากคลาส
เราจะนิยามคลาสรูปทรงโดยไม่สืบคุณสมบัติจาก base class

บรรทัด 16-24 คือนิยามคลาส Square ทำหน้าที่ใช้สร้างออพเจ็กต์สี่เหลี่ยม
บรรทัด 26-34 คือนิยามคลาส Circle ทำหน้าที่ใช้สร้างออพเจ็กต์วงกลม

รูปที่ 2
 


 

นิยาม structure Rectangle และ นิยามคลาส Triangle

บรรทัด 36-46 คือนิยาม structure Rectangle ทำหน้าที่ใช้สร้าง object สี่เหลี่ยมผืนผ้า
บรรทัด 48-54 คือนิยามคลาส Triangle ทำหน้าที่ใช้สร้าง object สามเหลี่ยม

โปรดสังเกตว่าแต่ละคลาสมีความเป็นเอกเทศ คือ ไม่ได้สืบคุณสมบัติร่วมใด ๆ และไม่ได้อ้างอิงถึงกัน
แต่ละคลาสมีแต่ส่วนเก็บข้อมูล ไม่มีโค้ดจัดการข้อมูล ส่วนที่เป็นโค้ดคงมีเพียง method constructorเท่านั้น

รูปที่ 3

method ComputeArea

วิธีการเดิม (ก่อน  C# 7.0 )  เป็นโค้ดเพื่อการแยกแยะคัดกรองว่า object อะไรเป็นobject อะไร

method  ComputeArea เป็นตัวอย่าง method ซึ่งทำหน้าที่คำนวณพื้นที่ของรูปทรง
แต่เนื่องจากรูปทรงแต่ละแบบต้องการการคำนวณที่แตกต่างกัน

ยกตัวอย่างเช่น ถ้าเป็นสี่เหลี่ยมก็คำนวณโดยนำด้านมาคูณด้าน แต่ถ้าเป็นวงกลมเราจะคำนวณพื้นที่ได้โดยรัศมีคูณรัศมีคูณค่าพาย
ดังนั้นเราจึงต้องตรวจสอบก่อนว่ามันคืออะไร ซึ่งสามารถทำได้โดยใช้คำสั่ง if ร่วมกับนิพจน์บูลีนที่มีตัวกระทำ is (ดูบรรทัด 65 และ 70)
เมื่อตรวจพบแล้วยังต้องแคส (อันบ็อกซ์)  เสียก่อนจึงจะนำไปประมวลผลได้

รูปที่ 4

ตัวอย่างโค้ดการตรวจสอบเพื่อการจับคู่รูปแบบ

ตัวอย่างโค้ดการตรวจสอบเพื่อการจับคู่รูปแบบ แบบใหม่ใน C# 7.0 จะเห็นว่าโค้ดคล้ายเดิม
แต่คราวนี้เราได้ตัวแปร s c และ r โดยไม่ต้องแคส (อันบ็อกซ์)  เสียก่อน

โดยตัวแปร s c และ r  จะเกิดก็ต่อเมื่อเงื่อนไขของนิพจน์บูลีนเป็นจริง
ตัวแปรแต่ละตัวจะไม่เกิดถ้านิพจน์บูลีนเป็นเท็จ และตัวแปรแต่ละตัวจะมีสโคปอยู่ภายใน if ของมันเอง
ยกเว้น s ที่กินสโคปในระดับเมธอด ComputeAreaModernIs

รูปที่ 5

เมธอด GenerateMessage

ในกรณีที่เป็นการคัดกรองจำนวนมาก
หากเขียนโดยใช้คำสั่ง if จะมีโค้ดรุงรังเกินไป เราจึงจะทดแทนด้วยคำสั่ง switch

ยกตัวอย่างใน รูปที่ 5  Method GenerateMessage เป็น Method ที่มี input parameter จำนวนไม่แน่นอน  

หากเราต้องการตรวจสอบจำนวนของพารามิเตอร์ เราสามารถทำได้โดยใช้คำสั่ง switch
โดยพิจารณาจาก property Length ของตัวแปร parts

ส่วนคำสั่ง case มีข้อจำกัดว่าจะต้องมีค่าเป็นชนิดข้อมูลแบบ “เลขจำนวนเต็ม” หรือ “string”
และต้องเป็นตัวคงค่าเท่านั้น (เป็นตัวแปรไม่ได้)

รูปที่ 6

ตัวอย่างการใช้คำสั่ง case แบบใหม่ที่ทำงานกับรีฟอร์เรนซ์ไทป์

case แบบใหม่และ when

ข้อจำกัดของคำสั่ง switch / case ที่กล่าวถึงในย่อหน้าบนถูกยกออกไปใน C# 7.0

นั่นคือต่อแต่นี้เราสามารถใช้ชนิดข้อมูลอะไรก็ได้
เป็นตัวคงค่าหรือตัวแปรก็ได้
จะเป็นชนิดข้อมูลแบบ “Reference Type”
หรือ “Value Type” ก็ได้ ร่วมกับคำสั่ง case

บรรทัดที่ 114, 116 เป็นตัวอย่างการใช้คำสั่ง case แบบใหม่ที่ทำงานกับ Reference Type (Square และ Circle เป็นคลาส) ให้ผลลัพธ์เป็นตัวแปร s และ c
บรรทัดที่ 118 เป็นตัวอย่างการใช้คำสั่ง case แบบใหม่ที่ทำงานกับ Value Type (Rectangle เป็น structure) ให้ผลลัพธ์เป็นตัวแปร r 
 
โปรดสังเกตว่า case แบบใหม่ไม่ต้องปิดก้อนด้วยคำสั่ง break อีกต่อไปแล้ว
เพราะการทำงานจะไม่ตกไปยัง case ถัดไปเหมือนแต่ก่อน

คุณสมบัติใหม่นี้ทำให้ลำดับของการเขียน case มีนัยสำคัญ
ไม่เหมือน case แบบเก่าที่เราสามารถสลับลำดับของมันไปมาอย่างไรก็ได้ตามใจชอบ จะไม่ทำให้เกิดความแตกต่างใด ๆ ในการทำงาน

รูปที่ 7
การใช้คำสั่ง when ร่วมกับคำสั่ง case
ความวิเศษของ case แบบใหม่ไม่ได้หมดเพียงแค่นั้น

เรายังสามารถใช้คำสั่ง when ร่วมกับคำสั่ง case ได้อีกด้วย ซึ่งช่วยให้การทำ PM มีโค้ดที่กระชับยิ่งไปขึ้นอีก

ยกตัวอย่างเช่น รูปที่ 7 บรรทัด 131

ในกรณีที่พบว่า shape เป็น Square จะเกิดตัวแปร s และ คำสั่ง when
จะตรวจสอบสืบไปได้อีกว่า property Side มีค่าเป็น 0 หรือไม่
หากใช่ ตัว run time จะทำงานแบบทัดที่ 133 
 
โปรดสังเกตว่าโค้ดบรรทัด 131 กับ 135 เป็น case ที่ซ้ำกัน (s) ซึ่ง C# 7.0 อนุญาตให้ทำได้

ท่านอาจสงสัยว่าแล้วบรรทัดไหนจะทำงานก่อน คำตอบคือบรรทัด 131
นั่นคือการทำงานของ case จะไล่ไปตามลำดับของซอร์สโค้ด
และถ้าเงื่อนไขของบรรทัด 131 เป็นจริง case อื่น ๆ ที่เหลือจะไม่มีโอกาสได้ทำงานเลย
เราจึงไม่ต้องกังวลว่าจะเกิดการทำงานซ้ำซ้อน

รูปที่ 8
ออพเจ็กต์มีนิพจน์เงื่อนไขของ when ที่ซับซ้อน

เพิ่มการตรวจสอบ object อีกสองแบบ คือ Triangle และ Rectangle

โดย object สองแบบนี้มีนิพจน์เงื่อนไขของ when ที่ซับซ้อนขึ้น
คือใช้ตัวกระทำ || (or) ทำตรรกบูลีนกับนิพจน์ย่อยสองนิพจน์ (บรรทัด 152,153) ด้วย

รูปที่ 9

ตัวอย่างการใช้คำสั่ง var เพื่อประกาศตัวแปรภายในคำสั่ง case

ตัวอย่างการใช้คำสั่ง var เพื่อประกาศตัวแปรภายในคำสั่ง case
ในกรณีนี้เราใช้คำสั่ง switch กับตัวแปรแบบ string

บรรทัด 203, 206, 209 เป็นการเทียบกับตัวคงค่าที่กำหนดไว้กับ case ซึ่งเป็นซินแท็กซ์ที่มีมาแต่เดิม
บรรทัด 212 เป็นคุณสมบัติใหม่ของ C# 7.0 ที่เราสามารถใส่คำสั่ง var เพื่อ ประกาศตัวแปร o (ใช้ชื่ออื่นได้)
เพื่อทำหน้าที่เก็บค่าของตัวแปร shapeDescription จากนั้นสามารถใช้ o เพื่อตรวจสอบได้ว่าค่าของมันเป็น “” ( string ว่าง) หรือไม่ 


 

โดยสรุปแล้ว Pattern Matching ใน C# 7 นั้น  จะช่วยให้การเขียน Code นั้นกระชับขึ้น
และจากข้อจำกัดเดิมของคำสั่ง switch/case ที่ไม่สามารถใช้ตัวแปรได้
แต่ใน C# 7 ทำให้เราสามารถใช้ชนิดข้อมูลอะไรก็ได้ เป็นตัวคงค่าหรือตัวแปรก็ได้ จะเป็นชนิดข้อมูลแบบ “Reference Type” หรือ “Value Type” ก็ได้ ร่วมกับคำสั่ง case
รวมถึงไม่ต้องกังวลว่าจะเกิดการทำงานซ้ำซ้อน เนื่องจากการทำงานของ case จะไล่ไปตามลำดับของซอร์สโค้ด หากเข้าเงื่อนไขใดแล้ว จะไม่ไปที่ case อื่น ๆ ถัดไป เหมือน case แบบเดิม
ซึ่งทำให้ case แบบใหม่ไม่ต้องปิดก้อนด้วยคำสั่ง break อีกต่อไปแล้ว เพราะการทำงานจะไม่ตกไปยัง case ถัดไปเหมือนแต่ก่อน
 
อย่างไร ทดลองใช้งาน  Pattern Matching ด้วย  switch/case แบบใหม่ ใน C# 7 ดูกันนะครับ