มีอะไรใหม่ใน .NET Core 2 และ C# 7 : คุณสมบัติ ref return ,ref local กับ Local Function

มีอะไรใหม่ใน .NET Core 2 และ C# 7 : คุณสมบัติ ref return ,ref local กับ Local Function
คุณสมบัติ ref return
คุณสมบัติ ref return เริ่มตั้งแต่ C# 7.0 จากนั้นพอมาถึง C# 7.3 ก็เพิ่มคุณสมบัติ ref local เพื่อให้ใช้งานคู่กัน ทำให้เขียนโค้ดฝั่ง method และ ฝั่งตัวรับได้สะดวกยิ่งขึ้นไปอีก
ปกตินักเขียนโค้ดอย่างเราจะใช้ตัวกระทำ ref (ความจริงเป็นmodifierแต่เพื่อให้กระชับต่อไปนี้จะเรียกว่าเป็น “คำสั่ง” ) เมื่อต้องการส่ง “ค่าอ้างอิง” ไปเป็นพารามิเตอร์ (คือการส่งค่าไปยังmethodโดยส่งค่าอ้างอิงไป ไม่ได้ส่งค่าของตัวแปรไป) หรือที่เรียกว่า “การผ่านตัวอ้างอิง” (pass by reference)
นี่เป็นคุณสมบัติที่มีใน C# ตั้งแต่ version แรก แต่มาตอนนี้เราสามารถใช้คำสั่ง ref ในลักษณะอื่น ๆ ได้ด้วย
ในตอนที่ผ่านมาผู้เขียนได้พูดถึงการการใช้ ref return ไปแล้วบางส่วน
ในบทความตอนนี้จะอธิบายรายละเอียดของ ref return ที่เหลือ และจะต่อด้วยเรื่องคุณสมบัติ ref local ด้วย
โค้ดตัวอย่างในตอนที่แล้วเราให้ method ส่งค่ากลับเป็นค่าอ้างอิงไปยังหน่วยใน metrix โดยตรง
ซึ่งทำได้โดยกำหนดให้ค่าส่งกลับเป็น Pointer ที่ชี้ไปยัง int และการค่ากลับเป็นแบบ ref
แต่ปัญหาคือคำสั่ง return ส่งค่าที่เป็น Value Type กลับไป ทำให้ compile โปรแกรมไม่ผ่าน
วิธีแก้คือการใช้สิ่งที่เรียกว่า “ref return” ซึ่งเป็นคุณสมบัติใหม่ที่อยู่ใน C# 7.3 ขึ้นไป
วิธีใช้คุณสมบัติ ref return ทำได้โดยใส่คำสั่ง ref ไว้ที่ค่าส่งกลับตอน return ด้วย
อย่างที่เห็นในรูปที่ 1 บรรทัดที่ 21 การทำเช่นนี้เป็นการบอกตัวแปลภาษาว่า เราต้องการส่งค่าอ้างอิงกลับ
และยังช่วยให้นักเขียนโปรแกรมผู้ที่จะต้องอ่านโค้ดนี้ภายหลังจำได้ว่า method นี้ส่งกลับเป็นค่าอ้างอิง
รูปที่ 1
ปกตินักเขียนโค้ดอย่างเราจะใช้ตัวกระทำ ref (ความจริงเป็นmodifierแต่เพื่อให้กระชับต่อไปนี้จะเรียกว่าเป็น “คำสั่ง” ) เมื่อต้องการส่ง “ค่าอ้างอิง” ไปเป็นพารามิเตอร์ (คือการส่งค่าไปยังmethodโดยส่งค่าอ้างอิงไป ไม่ได้ส่งค่าของตัวแปรไป) หรือที่เรียกว่า “การผ่านตัวอ้างอิง” (pass by reference)
นี่เป็นคุณสมบัติที่มีใน C# ตั้งแต่ version แรก แต่มาตอนนี้เราสามารถใช้คำสั่ง ref ในลักษณะอื่น ๆ ได้ด้วย
ในตอนที่ผ่านมาผู้เขียนได้พูดถึงการการใช้ ref return ไปแล้วบางส่วน
ในบทความตอนนี้จะอธิบายรายละเอียดของ ref return ที่เหลือ และจะต่อด้วยเรื่องคุณสมบัติ ref local ด้วย
โค้ดตัวอย่างในตอนที่แล้วเราให้ method ส่งค่ากลับเป็นค่าอ้างอิงไปยังหน่วยใน metrix โดยตรง
ซึ่งทำได้โดยกำหนดให้ค่าส่งกลับเป็น Pointer ที่ชี้ไปยัง int และการค่ากลับเป็นแบบ ref
แต่ปัญหาคือคำสั่ง return ส่งค่าที่เป็น Value Type กลับไป ทำให้ compile โปรแกรมไม่ผ่าน
วิธีแก้คือการใช้สิ่งที่เรียกว่า “ref return” ซึ่งเป็นคุณสมบัติใหม่ที่อยู่ใน C# 7.3 ขึ้นไป
วิธีใช้คุณสมบัติ ref return ทำได้โดยใส่คำสั่ง ref ไว้ที่ค่าส่งกลับตอน return ด้วย
อย่างที่เห็นในรูปที่ 1 บรรทัดที่ 21 การทำเช่นนี้เป็นการบอกตัวแปลภาษาว่า เราต้องการส่งค่าอ้างอิงกลับ
และยังช่วยให้นักเขียนโปรแกรมผู้ที่จะต้องอ่านโค้ดนี้ภายหลังจำได้ว่า method นี้ส่งกลับเป็นค่าอ้างอิง
รูปที่ 1
เนื่องจากตอนนี้ method ส่งกลับเป็นค่าอ้างอิงไปยังข้อมูลแบบจำนวนเต็มที่อยู่ภายใน metrix เราจึงต้องเปลี่ยนแปลงโค้ดเรียกใช้
รูปที่ 2 คือตัวอย่างโค้ดที่แสดงการเปลี่ยนแปลงนี้
คำสั่ง var ในบรรทัดที่ 13 หมายถึงตอนนี้ valItem เป็นเลขจำนวนเต็ม ไม่ได้เป็น tupleเ หมือนตัวอย่างก่อน
รูปที่ 2
คำสั่ง WriteLine ในบรรทัดที่ 16 ให้ผลลัพธ์เป็น 42 ไม่ใช่ 24
ตัวแปร valItem เป็นเลขจำนวนเต็ม ไม่ใช่ค่าอ้างอิงไปยังเลขจำนวนเต็ม
คำสั่ง var บอกให้ตัวแปลภาษากำหนดชนิดของข้อมูลเอาเอง โดยดูจากบริบท ตัวแปลภาษาก็ทำได้ แต่ไม่ได้ใส่ modifierref เข้าไปให้ด้วย
แทนที่จะเป็นแบบนี้ค่าที่ถูกอ้างอิงโดย ref return จะถูกทำสำเนาไปใส่ในตัวแปรที่อยู่ทางซ้ายมือของการกำหนดค่า
แต่ตัวแปรไม่ได้เป็น ref local เราจึงไม่ได้ผลลัพธ์ตามที่ต้องการ
รูปที่ 3
ตัวแปร valItem เป็นเลขจำนวนเต็ม ไม่ใช่ค่าอ้างอิงไปยังเลขจำนวนเต็ม
คำสั่ง var บอกให้ตัวแปลภาษากำหนดชนิดของข้อมูลเอาเอง โดยดูจากบริบท ตัวแปลภาษาก็ทำได้ แต่ไม่ได้ใส่ modifierref เข้าไปให้ด้วย
แทนที่จะเป็นแบบนี้ค่าที่ถูกอ้างอิงโดย ref return จะถูกทำสำเนาไปใส่ในตัวแปรที่อยู่ทางซ้ายมือของการกำหนดค่า
แต่ตัวแปรไม่ได้เป็น ref local เราจึงไม่ได้ผลลัพธ์ตามที่ต้องการ
รูปที่ 3
คุณสมบัติ ref local
วิธีแก้ไขเพื่อให้ได้ผลลัพธ์อย่างที่ต้องการ เราต้องใช้สิ่งที่เรียกว่า “ref local”
ซึ่งทำได้โดยเพิ่มคำสั่ง ref ไว้ที่หน้าส่วนประกาศของตัวแปรท้องถิ่น
เพื่อทำให้ตัวแปรกลายเป็นแบบอ้างอิงเมื่อค่าส่งกลับเป็นค่าอ้างอิง
รูปที่ 3 บรรทัดที่ 27 ให้ผลลัพธ์เป็น 24 ไม่ใช่ 42 เหมือนตัวอย่างก่อนหน้านี้
แสดงให้เห็นว่าส่วนเก็บข้อมูลภายในเมทริกซ์ถูกเปลี่ยนแปลงค่าไปแล้ว
ตัวแปรท้องถิ่นถูกประกาศโดยใส่คำสั่ง ref นำหน้า ทำให้ค่าที่มันรับเป็นค่าส่งกลับจากmethod ถูกนำไปปฏิบัติอย่างค่าอ้างอิง
การประกาศตัวแปรแบบ ref local นี้ เราจำเป็นต้องประกาศและกำหนดค่าเริ่มต้นไว้ในบรรทัดเดียวกัน
จะกระกาศไว้บรรทัดหนึ่งแล้วไปเขียนโค้ดกำหนดค่าอีกบรรทัดหนึ่งอย่างตัวแปรทั่วไปไม่ได้
และจะนำตัวแปรแบบ ref local ไปรับค่าส่งกลับจากmethodธรรมดาไม่ได้ (คือ ref int i = sequence.Count();) ไม่ได้
รูปที่ 4
ซึ่งทำได้โดยเพิ่มคำสั่ง ref ไว้ที่หน้าส่วนประกาศของตัวแปรท้องถิ่น
เพื่อทำให้ตัวแปรกลายเป็นแบบอ้างอิงเมื่อค่าส่งกลับเป็นค่าอ้างอิง
รูปที่ 3 บรรทัดที่ 27 ให้ผลลัพธ์เป็น 24 ไม่ใช่ 42 เหมือนตัวอย่างก่อนหน้านี้
แสดงให้เห็นว่าส่วนเก็บข้อมูลภายในเมทริกซ์ถูกเปลี่ยนแปลงค่าไปแล้ว
ตัวแปรท้องถิ่นถูกประกาศโดยใส่คำสั่ง ref นำหน้า ทำให้ค่าที่มันรับเป็นค่าส่งกลับจากmethod ถูกนำไปปฏิบัติอย่างค่าอ้างอิง
การประกาศตัวแปรแบบ ref local นี้ เราจำเป็นต้องประกาศและกำหนดค่าเริ่มต้นไว้ในบรรทัดเดียวกัน
จะกระกาศไว้บรรทัดหนึ่งแล้วไปเขียนโค้ดกำหนดค่าอีกบรรทัดหนึ่งอย่างตัวแปรทั่วไปไม่ได้
และจะนำตัวแปรแบบ ref local ไปรับค่าส่งกลับจากmethodธรรมดาไม่ได้ (คือ ref int i = sequence.Count();) ไม่ได้
รูปที่ 4
รูปที่ 4 คือตัวอย่างโค้ดแสดงการใช้งาน ref return อีกตัวอย่างหนึ่ง
บรรทัดที่ 24 ถึง 39 คือ นิยาม class NumberStore ซึ่งทำหน้าที่เก็บ array แบบเลขจำนวนเต็ม
บรรทัดที่ 28 ถึง 36 คือ นิยาม method FindNumber ที่มีค่าส่งกลับเป็นแบบ ref return
โดยมันจะส่งค่าอ้างอิงของตัวเลขตัวแรกที่มีค่ามากว่าหรือเท่ากับตัวเลขในพารามิเตอร์ target
แต่ถ้าไม่มีตัวเลขที่มีค่ามากว่าหรือเท่ากับตัวเลขในพารามิเตอร์ target มันจะส่งกลับค่าที่อ้างอิงไปยังจำนวนแรกของ array แทน
รูปที่ 5
บรรทัดที่ 24 ถึง 39 คือ นิยาม class NumberStore ซึ่งทำหน้าที่เก็บ array แบบเลขจำนวนเต็ม
บรรทัดที่ 28 ถึง 36 คือ นิยาม method FindNumber ที่มีค่าส่งกลับเป็นแบบ ref return
โดยมันจะส่งค่าอ้างอิงของตัวเลขตัวแรกที่มีค่ามากว่าหรือเท่ากับตัวเลขในพารามิเตอร์ target
แต่ถ้าไม่มีตัวเลขที่มีค่ามากว่าหรือเท่ากับตัวเลขในพารามิเตอร์ target มันจะส่งกลับค่าที่อ้างอิงไปยังจำนวนแรกของ array แทน
รูปที่ 5
รูปที่ 5 คือตัวอย่างโค้ดแสดงการใช้งาน ref local
บรรทัด 13 สร้าง Object จากคลาส NumberStore ที่นิยามตามโค้ดก่อนหน้านี้
บรรทัด 14 แสดงข้อมูลใน array ทั้งหมดเรียงตามลำดับปกติ ผลลัพธ์เป็นอย่างบรรทัด 20
บรรทัด 15 ประกาศตัวแปร number เพื่อเก็บจำนวนที่ต้องการค้นหา
บรรทัด 16 เรียกใช้ method NumberStore.FindNumber ซึ่งจะหาตัวเลขตัวแรกที่มีค่ามากว่าหรือเท่ากับ 16
บรรทัด 13 สร้าง Object จากคลาส NumberStore ที่นิยามตามโค้ดก่อนหน้านี้
บรรทัด 14 แสดงข้อมูลใน array ทั้งหมดเรียงตามลำดับปกติ ผลลัพธ์เป็นอย่างบรรทัด 20
บรรทัด 15 ประกาศตัวแปร number เพื่อเก็บจำนวนที่ต้องการค้นหา
บรรทัด 16 เรียกใช้ method NumberStore.FindNumber ซึ่งจะหาตัวเลขตัวแรกที่มีค่ามากว่าหรือเท่ากับ 16
บรรทัด 17 เพิ่มค่าในตัวแปร value ขึ้นอีกเท่าตัว
บรรทัด 14 แสดงข้อมูลในอาร์เรย์เรียงตามลำดับอีกครั้ง จะพบว่าค่าของหน่วยอาร์เรย์ดรรชนีที่ 4 เปลี่ยนแปลงไป
ที่เป็นเช่นนั้นเพราะการเปลี่ยนแปลงค่าของตัวแปร value ได้เปลี่ยนแปลงค่าใน array ด้วย เพราะค่าของ value เป็นค่าที่อ้างอิงไปยังหน่วยใน array
รูปที่ 6
บรรทัด 14 แสดงข้อมูลในอาร์เรย์เรียงตามลำดับอีกครั้ง จะพบว่าค่าของหน่วยอาร์เรย์ดรรชนีที่ 4 เปลี่ยนแปลงไป
ที่เป็นเช่นนั้นเพราะการเปลี่ยนแปลงค่าของตัวแปร value ได้เปลี่ยนแปลงค่าใน array ด้วย เพราะค่าของ value เป็นค่าที่อ้างอิงไปยังหน่วยใน array
รูปที่ 6
ถ้าไม่มีคุณสมบัติ ref return และ ref local และเราต้องเขียนโค้ดอย่างที่มีการทำงานตามที่กล่าวมา
เราจำเป็นต้องส่งค่ากลับสองตัว คือ ค่าดรรชนีและค่าของข้อมูลในดรรชนีนั้น
โค้ดที่เรียกใช้สามารถใช้ค่าดรรชนีเพื่อไปแก้ไขค่าใน array โดยการเรียกอีก method หนึ่ง
อย่างไรก็ดีเราอาจแก้ไขโค้ดส่วนเรียกใช้ให้นำดรรชนีไปใช้เข้าถึงและแก้ไขค่าของ array อื่น ๆ ได้ด้วย
รูปที่ 6 คือ ตัวอย่างโค้ดแสดงวิธีนิยาม class NumberStore และ method FindNumber
โดยแก้ไขโค้ดให้ใช้ ref local ที่มีใน C# 7.3 ขึ้นไป
บรรทัด 21 คือการประกาศตัวแปร returnVal ซึ่งเป็นตัวแปรแบบ ref local
บรรทัด 28 แสดงการนำตัวแปร returnVal มาใช้เป็นค่าส่งกลับ
โปรดสังเกตว่าจำเป็นต้องใส่คำสั่ง ref นำหน้าตัวแปรด้วย
เราจำเป็นต้องส่งค่ากลับสองตัว คือ ค่าดรรชนีและค่าของข้อมูลในดรรชนีนั้น
โค้ดที่เรียกใช้สามารถใช้ค่าดรรชนีเพื่อไปแก้ไขค่าใน array โดยการเรียกอีก method หนึ่ง
อย่างไรก็ดีเราอาจแก้ไขโค้ดส่วนเรียกใช้ให้นำดรรชนีไปใช้เข้าถึงและแก้ไขค่าของ array อื่น ๆ ได้ด้วย
รูปที่ 6 คือ ตัวอย่างโค้ดแสดงวิธีนิยาม class NumberStore และ method FindNumber
โดยแก้ไขโค้ดให้ใช้ ref local ที่มีใน C# 7.3 ขึ้นไป
บรรทัด 21 คือการประกาศตัวแปร returnVal ซึ่งเป็นตัวแปรแบบ ref local
บรรทัด 28 แสดงการนำตัวแปร returnVal มาใช้เป็นค่าส่งกลับ
โปรดสังเกตว่าจำเป็นต้องใส่คำสั่ง ref นำหน้าตัวแปรด้วย
ฟังก์ชันท้องถิ่น
ในการออกแบบclassบ่อยครั้งที่เราต้องนิยาม method ที่ถูกเรียกจากเพียงที่เดียว
การทำแบบนี้ไม่ผิดเพราะช่วยให้เรามี method แบบ private ที่เล็กกะทัดรัด และ มุ่งงานหนึ่ง ๆ
แต่ถ้ามี method แบบนี้เป็นจำนวนมากโค้ดก็จะรกไปหมด มีผลให้ผู้อ่านโค้ดของ class เมื่ออ่านโค้ดในครั้งแรกจะทำความเข้าใจการทำงานของ class ได้ยาก
เพื่อแก้ปัญหานี้ C# 7.0 ขึ้นไปจึงเพิ่มคุณสมบัติที่เรียกว่า “ฟังก์ชันท้องถิ่น” (Local function)
ซึ่งมีลักษณะทำนองเดียวกับ “ตัวแปรท้องถิ่น” (local variable) ที่หมายถึงตัวแปรภายใน method
“ฟังก์ชันท้องถิ่น” ก็ หมายถึงฟังก์ชันที่อยู่ภายใน method และให้เรียกใช้ได้เฉพาะใน method นั้น ๆ เท่านั้น
การทำเช่นนี้จะช่วยให้ผู้อ่านโค้ดของ class สามารถทำความเข้าใจการทำงานของ classได้ง่ายขึ้น
รูปที่ 7
การทำแบบนี้ไม่ผิดเพราะช่วยให้เรามี method แบบ private ที่เล็กกะทัดรัด และ มุ่งงานหนึ่ง ๆ
แต่ถ้ามี method แบบนี้เป็นจำนวนมากโค้ดก็จะรกไปหมด มีผลให้ผู้อ่านโค้ดของ class เมื่ออ่านโค้ดในครั้งแรกจะทำความเข้าใจการทำงานของ class ได้ยาก
เพื่อแก้ปัญหานี้ C# 7.0 ขึ้นไปจึงเพิ่มคุณสมบัติที่เรียกว่า “ฟังก์ชันท้องถิ่น” (Local function)
ซึ่งมีลักษณะทำนองเดียวกับ “ตัวแปรท้องถิ่น” (local variable) ที่หมายถึงตัวแปรภายใน method
“ฟังก์ชันท้องถิ่น” ก็ หมายถึงฟังก์ชันที่อยู่ภายใน method และให้เรียกใช้ได้เฉพาะใน method นั้น ๆ เท่านั้น
การทำเช่นนี้จะช่วยให้ผู้อ่านโค้ดของ class สามารถทำความเข้าใจการทำงานของ classได้ง่ายขึ้น
รูปที่ 7
มีสถานะการณ์สองอย่างที่เรานำ “ฟังก์ชันท้องถิ่น” มาใช้ช่วยได้ดี คือ
- การเขียน method แบบ Public ที่เป็น iterator และ
- การเขียน method แบบ Public ที่เป็น async
ในกรณี iterator จะเกิด error ก็ต่อเมื่อเราเรียกโค้ดให้วนค่าส่งกลับที่เรียงลำดับ
หรือในกรณี async จะเกิด error ก็ต่อเมื่อ task จบการทำงาน
รูปที่ 7 คือ โค้ดตัวอย่าง method แบบ Public ที่เป็น iterator
บรรทัด 19 ถึง 34 คือ class Iterator ที่มี method AlphabetSubset
ซึ่งเป็น method ที่มีค่าส่งกลับเป็น IEnumerable<char>
มีผลให้โค้ดที่เรียกใช้งาน method นี้สามารถเขียนโค้ดวนค่าส่งกลับได้เหมือนกับว่ามันเป็น object collection
method นี้ มีพารามิเตอร์สองตัว (start และ end)
บรรทัด 24 ตรวจสอบว่า start เป็นตัวอักษรหรือไม่
บรรทัด 26 ตรวจสอบว่า end เป็นตัวอักษรหรือไม่
บรรทัด 29 ตรวจสอบว่าค่าของ end น้อยกว่า start หรือไม่
บรรทัด 31 คือ โค้ดที่วนการทำงานระหว่าง start และ end
บรรทัด 32 คำสั่ง yield return ทำให้สามารถส่งค่ากลับเป็นค่าเดียว ๆ แต่โค้ดที่เรียกใช้สามารถวนซ้ำเรียกค่าลำดับถัดไปเรื่อยจนกว่าจะหมดได้
รูปที่ 8
บรรทัด 19 ถึง 34 คือ class Iterator ที่มี method AlphabetSubset
ซึ่งเป็น method ที่มีค่าส่งกลับเป็น IEnumerable<char>
มีผลให้โค้ดที่เรียกใช้งาน method นี้สามารถเขียนโค้ดวนค่าส่งกลับได้เหมือนกับว่ามันเป็น object collection
method นี้ มีพารามิเตอร์สองตัว (start และ end)
บรรทัด 24 ตรวจสอบว่า start เป็นตัวอักษรหรือไม่
บรรทัด 26 ตรวจสอบว่า end เป็นตัวอักษรหรือไม่
บรรทัด 29 ตรวจสอบว่าค่าของ end น้อยกว่า start หรือไม่
บรรทัด 31 คือ โค้ดที่วนการทำงานระหว่าง start และ end
บรรทัด 32 คำสั่ง yield return ทำให้สามารถส่งค่ากลับเป็นค่าเดียว ๆ แต่โค้ดที่เรียกใช้สามารถวนซ้ำเรียกค่าลำดับถัดไปเรื่อยจนกว่าจะหมดได้
รูปที่ 8
รูปที่ 8 คือ ตัวอย่างการเรียก method AlphabetSubset อย่างผิด ๆ
เพราะบรรทัด 13 เรากำหนดให้พารามิเตอร์ f มาก่อน a
error หรือ exception จะปรากฏตอนตัวแปร resultSet ถูกวน (บรรทัด 15) ไม่ใช่ตอนถูกประกาศ (บรรทัด 13)
ถ้าเป็นโปรแกรมสั้น ๆ อย่างในตัวอย่างนี้ คนโค้ดส่วนมากจะวิเคราะห์ปัญหาได้อย่างรวดเร็ว
แต่ในกรณีที่เป็นกลุ่มโค้ดขนาดใหญ่ โค้ดส่วนที่สร้างตัว iterator อาจไม่ได้อยู่ติดกับโค้ดส่วนที่วนข้อมูลผลลัพธ์ ทำให้ต้องไล่โค้ดนานและจะวิเคราะห์ปัญหาได้ยากกว่า
รูปที่ 9
เพราะบรรทัด 13 เรากำหนดให้พารามิเตอร์ f มาก่อน a
error หรือ exception จะปรากฏตอนตัวแปร resultSet ถูกวน (บรรทัด 15) ไม่ใช่ตอนถูกประกาศ (บรรทัด 13)
ถ้าเป็นโปรแกรมสั้น ๆ อย่างในตัวอย่างนี้ คนโค้ดส่วนมากจะวิเคราะห์ปัญหาได้อย่างรวดเร็ว
แต่ในกรณีที่เป็นกลุ่มโค้ดขนาดใหญ่ โค้ดส่วนที่สร้างตัว iterator อาจไม่ได้อยู่ติดกับโค้ดส่วนที่วนข้อมูลผลลัพธ์ ทำให้ต้องไล่โค้ดนานและจะวิเคราะห์ปัญหาได้ยากกว่า
รูปที่ 9
เราอาจเปลี่ยนแปลง method AlphabetSubset เสียใหม่
โดยแยกออกเป็น 2 method อย่างที่เห็นในรูปที่ 9
บรรทัด 11 ถึง 21 เป็น method แบบ Public ให้มีเฉพาะโค้ดตรวจสอบความผิดพลาด
ส่วนโค้ดที่ทำหน้าที่วนการทำงานถูกแยกออกไปทำเป็น private method คือ method alphabetSubsetImplementation บรรทัด 23 ถึง 27
เมื่อเปลี่ยนแปลงโค้ดเป็นอย่างนี้แล้ว error หรือ exception จะปรากฏตอนตัวแปร resultSet ถูกประกาศทันที
เพราะ method AlphabetSubset ไม่ใช่ method ที่ทำหน้าที่วนข้อมูล คงมีแต่ method alphabetSubsetImplementation เท่านั้นที่มีคำสั่ง yield return
แต่การเปลี่ยนโค้ดให้เป็นแบบนี้แม้จะทำให้ error ย้ายไปอยู่ในตำแหน่งที่ดีขึ้น แต่ก็ยังมีข้อเสียอยู่หลายอย่าง
เช่นเราควรเรียกใช้ method alphabetSubsetImplementation จากmethod AlphabetSubset เท่านั้น
เพราะถ้าเรียกจากmethodอื่น ๆ argument จะไม่ได้รับการตรวจสอบคัดกรอง
แต่กว่าที่ผู้อ่านโค้ดจะรู้ความจริงนี้ได้ก็ต้องไล่โค้ดไปจนจบ class เสียก่อน
แล้วค้นหาว่ามีโค้ดตรงไหนบ้างที่อ้างถึง method alphabetSubsetImplementation บ้าง ซึ่งสิ้นเปลืองเวลามาก
รูปที่ 10
โดยแยกออกเป็น 2 method อย่างที่เห็นในรูปที่ 9
บรรทัด 11 ถึง 21 เป็น method แบบ Public ให้มีเฉพาะโค้ดตรวจสอบความผิดพลาด
ส่วนโค้ดที่ทำหน้าที่วนการทำงานถูกแยกออกไปทำเป็น private method คือ method alphabetSubsetImplementation บรรทัด 23 ถึง 27
เมื่อเปลี่ยนแปลงโค้ดเป็นอย่างนี้แล้ว error หรือ exception จะปรากฏตอนตัวแปร resultSet ถูกประกาศทันที
เพราะ method AlphabetSubset ไม่ใช่ method ที่ทำหน้าที่วนข้อมูล คงมีแต่ method alphabetSubsetImplementation เท่านั้นที่มีคำสั่ง yield return
แต่การเปลี่ยนโค้ดให้เป็นแบบนี้แม้จะทำให้ error ย้ายไปอยู่ในตำแหน่งที่ดีขึ้น แต่ก็ยังมีข้อเสียอยู่หลายอย่าง
เช่นเราควรเรียกใช้ method alphabetSubsetImplementation จากmethod AlphabetSubset เท่านั้น
เพราะถ้าเรียกจากmethodอื่น ๆ argument จะไม่ได้รับการตรวจสอบคัดกรอง
แต่กว่าที่ผู้อ่านโค้ดจะรู้ความจริงนี้ได้ก็ต้องไล่โค้ดไปจนจบ class เสียก่อน
แล้วค้นหาว่ามีโค้ดตรงไหนบ้างที่อ้างถึง method alphabetSubsetImplementation บ้าง ซึ่งสิ้นเปลืองเวลามาก
รูปที่ 10
ใน C# version 7.0 ขึ้นไปให้ทางออกสำหรับปัญหานี้ ด้วยการใช้ฟังก์ชันท้องถิ่น
รูปที่ 10 คือ ตัวอย่างโค้ดแสดงการนำฟังก์ชันท้องถิ่นมาแก้ปัญหาที่พบในโค้ดตัวอย่าง 2 โค้ดก่อนหน้านี้
โค้ดตัวอย่างนี้ช่วยให้เห็นเจตนาการออกแบบได้ชัดเจน
เพราะเปลี่ยน method alphabetSubsetImplementation ให้กลายเป็นฟังก์ชันท้องถิ่นที่นิยามไว้ภายใน method แบบ Public ซึ่งเป็นส่วนติดต่อกับโลกภายนอก
สิ่งที่ผู้ไล่โค้ดจะเห็นได้อย่างรวดเร็วและชัดเจนคือ ผู้เขียนโค้ดออกแบบให้ method alphabetSubsetImplementation ถูกเรียกใช้งานได้จาก method AlphabetSubset3 เท่านั้น
ไม่ต้องไปไล่หาเลยว่ายังมีโค้ดตรงไหนใช้ method alphabetSubsetImplementation อีกบ้าง เพราะโค้ดที่อยู่ภายนอก method ไม่สามารถเรียกฟังก์ชันท้องถิ่นของmethodอื่นได้
รูปที่ 11
รูปที่ 10 คือ ตัวอย่างโค้ดแสดงการนำฟังก์ชันท้องถิ่นมาแก้ปัญหาที่พบในโค้ดตัวอย่าง 2 โค้ดก่อนหน้านี้
โค้ดตัวอย่างนี้ช่วยให้เห็นเจตนาการออกแบบได้ชัดเจน
เพราะเปลี่ยน method alphabetSubsetImplementation ให้กลายเป็นฟังก์ชันท้องถิ่นที่นิยามไว้ภายใน method แบบ Public ซึ่งเป็นส่วนติดต่อกับโลกภายนอก
สิ่งที่ผู้ไล่โค้ดจะเห็นได้อย่างรวดเร็วและชัดเจนคือ ผู้เขียนโค้ดออกแบบให้ method alphabetSubsetImplementation ถูกเรียกใช้งานได้จาก method AlphabetSubset3 เท่านั้น
ไม่ต้องไปไล่หาเลยว่ายังมีโค้ดตรงไหนใช้ method alphabetSubsetImplementation อีกบ้าง เพราะโค้ดที่อยู่ภายนอก method ไม่สามารถเรียกฟังก์ชันท้องถิ่นของmethodอื่นได้
รูปที่ 11
เราอาจนำวิธีการเดียวกันนี้ไปประยุกต์ใช้กับ method แบบ async ได้
รูปที่ 11 คือตัวอย่างโค้ดที่นำฟังก์ชันท้องถิ่นมาใช้ใน method แบบ async เพื่อแยกเอาโค้ดส่วนการทำงานอื่น ๆ
เช่นการคัดกรอง error (บรรทัด 14 ถึง 22) กับ โค้ดส่วนที่สร้างการทำงานคู่ขนาน (บรรทัดที่ 28,29)
การทำเช่นนี้มีผลให้ error จากการตรวจสอบ argument เกิดก่อนการทำงานคู่ขนาน
รูปที่ 11 คือตัวอย่างโค้ดที่นำฟังก์ชันท้องถิ่นมาใช้ใน method แบบ async เพื่อแยกเอาโค้ดส่วนการทำงานอื่น ๆ
เช่นการคัดกรอง error (บรรทัด 14 ถึง 22) กับ โค้ดส่วนที่สร้างการทำงานคู่ขนาน (บรรทัดที่ 28,29)
การทำเช่นนี้มีผลให้ error จากการตรวจสอบ argument เกิดก่อนการทำงานคู่ขนาน
จากบทความนี้ ท่านผู้อ่านน่าจะเห็นประโยชน์ของ ref local ว่าใช้คู่กับ ref return แล้วจะทำให้เขียนโค้ดฝั่ง method และ ฝั่งตัวรับได้สะดวกยิ่งขึ้นไปอีกได้อย่างไร
รวมถึงการนำฟังก์ชันท้องถิ่นมาใช้ ซึ่งมีประโยชน์ ทำให้ argument ได้รับการตรวจสอบคัดกรองก่อน ซึ่งส่งผลให้ไม่ต้องไล่โค้ดนานและจะวิเคราะห์ปัญหาได้ง่ายและเร็วขึ้น