LINQ
🤔 ทำงานกับข้อมูลมหาศาลใน .NET เขาทำกันยังไงนะ (สาย .NET ไม่รู้ไม่ได้)
ในบทนี้เราจะมาทำความรู้จักกับหนึ่งในความสามารถที่ทรงพลังที่สุดของภาษา C# เลยก็ได้ว่า นั่นก็คือเจ้าสิ่งที่เรียกว่า LINQ ซึ่งย่อมาจาก Language Integrated Query นั่นเอง โดยเจ้าตัวนี้เป็นหนึ่งมหากาพย์ที่ทำให้เราลดโค้ดจากร้อยๆบรรทัดให้เหลือแค่เพียงไม่กี่บรรทัดได้ และยังช่วยให้โค้ดที่เขียนกลายเป็น Clean Code อีกด้วยนะ เราลองไปทำความเข้าใจเรื่องของ LINQ กันเลยเลยดีกว่าครัช
🤔 LINQ คือไรหว่า ?
แบบสั้นๆก่อนเจ้า LINQ คือ ชุดคำสั่งที่จะทำให้เราทำงานกับกลุ่มของข้อมูลได้ง่ายๆ เช่น ทำงานกับ ข้อมูลที่ดึงมาจากฐานข้อมูล ทำงานกับ XML หรือพวก collection ต่างๆ โดยมีภาษาที่ใกล้เคียงกับ SQL syntax นั่นเอง
🤔 LINQ ใช้ไงหว่า ?
ซึ่งจากประสบการณ์ที่ผมไปสอนมาพบว่า เราไปดูตัวอย่างกันแล้วจะเข้าใจ LINQ ได้เร็วกว่าอ่านทฤษฎีครับ ดังนั้นผมจะใช้ตัวอย่างนี้อธิบายเอานะ โดยโจทย์ของผมคือถ้าผมมีข้อมูลตัวเลข 1~7 อยู่ใน array ตามโค้ดด้านล่าง
var numbers = new int[] { 1, 2, 3, 4, 5, 6, 7 };แล้วถ้าเกิดผมอยากดึงเฉพาะเลขคู่ออกมาจากตัวแปร numbers ล่ะต้องทำยังไง ? ซึ่งถ้าเขียนโค้ดแบบปรกติเราก็จะเขียนออกมาได้ราวๆนี้
🔥 เขียนแบบปรกติ
ผมก็จะสร้าง array ขึ้นใหม่ เพื่อเอาไว้เก็บค่าเฉพาะเลขคู่ไว้ยังไงล่ะ ตามโค้ดด้านล่างเลย
var evenIndex = 0;
var evenNumbers = new int[3];
for (int i = 0; i < numbers.Length; i++)
{
if (numbers[i] % 2 == 0)
{
evenNumbers[evenIndex++] = numbers[i];
}
}🔥 เขียนแบบใช้ LINQ
คราวนี้เราก็จะมาลองเอา LINQ มาแก้โจทย์เดียวกันดูบ้างนะ ซึ่งการที่จะใช้ LINQ ได้นั้นเราจะต้องเรียกใช้ using System.Linq; ไว้ด้านบนสุดด้วยนะ และการเขียน LINQ เราสามารถเขียนได้ 2 วิธีตามนี้
1.เขียน LINQ แบบเต็มๆ
ตาไม่ได้ฝาดไปหรอกครับ โค้ดมันเหลือแค่นั้นจริงๆ และมันเขียนแทบจะเหมือนภาษา SQL syntax เลยยังไงล่ะ ดังนั้นใครที่เขียน SQL syntax เป็นอยู่แล้วรับรองครับว่าสบายเลย
อธิบายโค้ดตามบรรทัด บรรทัดที่ 1 เราเลือกว่าจะทำงานกับกลุ่มข้อมูลตัวไหน ซึ่งในที่นี้คือ numbers นั่นเอง โดยข้อมูลแต่ละตัวในกลุ่มข้อมูลนั้นเราจะใช้ตัวแปรที่ชื่อว่า it เข้าไปไล่ค่ามัน (เหมือน foreach แหละ) บรรทัดที่ 2 เราทำการคัดกรองเอาเฉพาะข้อมูลตัวที่มันถูกหารด้วย 2 ลงตัวเท่านั้น ด้วยคำสั่ง where บรรทัดที่ 3 ข้อมูลไหนที่ผ่านเงื่อนไขจากบรรทัดที่ 2 เราจะทำการเอาข้อมูลเหล่านั้นมาใช้
2.เขียน LINQ แบบย่อๆ ที่เห็นมันสั้นแล้วจริงๆมันยังเขียนให้กระชับลงได้อีกตามโค้ดด้านล่างเลย
ไม่ว่าจะเป็น แบบเต็ม หรือ แบบย่อ การทำงานมันเหมือนกันครับ ขึ้นอยู่กับว่าถนัดแบบไหน ซึ่งปรกติผมแนะนำว่าให้เขียนแบบย่อครับ
🤔 หัวใจของ LINQ มีไรบ้าง ?
อยากเขียน LINQ เป็นจริงๆนั้นง่ายมาก เพราะ LINQ มีหัวใจการทำงานแค่ 3 เรื่องเท่านั้น โดยผมจะใช้โค้ดที่ใช้แก้โจทญืในตอนแรก มาเขียนกำกับหัวใจการทำงานลงไปละกัน ชึบๆ
ซึ่งจากโค้ดด้านบนจะเห็นว่ามันมีการแบ่งงานออกเป็น 3 เรื่องคือ
🔥 Data Source
คือกลุ่มข้อมูลที่เราต้องการจะทำงานด้วย ซึ่งกลุ่มข้อมูลในที่นี้คืออะไรก็ได้ที่เป็นตระกูล collection ที่มาจาก IEnumerable นั้นเอง ซึ่ง array ก็เป็นหนึ่งในนั้น เราเลยสามารถใช้ LINQ ทำงานด้วยได้
🔥 Query
คือคำสั่งที่เราต้องการจะไปกระทำกับ Data Source ของเรา ซึ่งจากโค้ดตัวอย่างคือเราจะทำการ filter เอาเฉพาะข้อมูลตัวที่ 2 หารลงตัวนั่นเอง
🔥 Query execution
คือตัวสั่งให้ query ที่เราเตรียมไว้มันเริ่มทำงาน ซึ่งในโค้ดคือเจ้า foreach นั่นเอง มันจะเป็นตัวกระตุ้นให้โปรแกรมเริ่มวิ่งเข้าไปที่ data source เพื่อดึงข้อมูลมาตาม query ที่เขียนไว้
🤔 อยากเขียน LINQ ต้องทำไง ?
เราก็แค่เรียนรู้ว่า LINQ มันสามารถเล่นอะไรกับกลุ่มของข้อมูลได้บ้างเพียงเท่านี้ก็ใช้ LINQ ได้เลย ส่วนถ้าอยากรีดศักยภาพมากขึ้นเราต้องเข้าใจการใช้ Lambda กับ Generic ด้วยจะดีมาก ดังนั้นเราลองไปดูว่า LINQ ทำอะไรกับกลุ่มของข้อมูลได้บ้างกัน
🔥 คัดกรองข้อมูล (Filtering)
ถ้าเราอยากคัดกรองข้อมูลให้มันเอาเฉพาะของที่เราอยากได้เท่านั้นออกมา เราสามารถใช้คำสั่ง Where ในการคัดกรองได้ เช่น ถ้าเรามีโค้ดเป็นแบบนี้
แล้วเราอยากได้เฉพาะตัวเลขที่มากกว่า 4 ขึ้นไป เราก็จะเขียนโค้ดออกมาเป็นตามนี้
ผลลัพท์ { 5, 6, 7 }
หรือเราอยากคัดกรองเอาเฉพาะ เลขคี่ ที่มากกว่า 2 ขึ้นไป
ผลลัพท์ { 3, 5, 7 }
🔥 เรียงลำดับ (Ordering)
ในกรณีที่ data source ของเราไม่ได้เรียงลำดับมา เราสามารถทำให้มันเรียงลำดับให้เราได้ เช่นผมมี data source เป็นแบบนี้
เรียงจากน้อยไปมาก
ผลลัพท์ { 1, 2, 3, 4, 5, 6, 7 }
เรียงจากมากไปหาน้อย
ผลลัพท์ { 7, 6, 5, 4, 3, 2, 1 }
🔥 จัดกลุ่ม (Grouping)
เราสามารถสร้างทำให้มันแบ่งข้อมูลออกเป็นกลุ่มๆตามเงื่อนไขได้ เช่น จากโค้ดตัวนี้
ผมต้องการจะให้มันแบ่งออกเป็น 2 กลุ่มคือ กลุ่มเลขคู่ กับ กลุ่มเลขคี่ ก็จะเขียนออกมาได้ประมาณนี้ (ขี้เกียจเขียนแบบเต็มแล้วนะ)
ผลลัพท์ กลุ่ม false จะมีข้อมูลเป็น 1, 3, 5, 7 กลุ่ม true จะมีข้อมูลเป็น 2, 4, 6
เอาเท่านี้ก่อนละกัน เพราะไม่งั้นมันจะเยอะมากจนบทความนี้อาจจะอ่านแล้วกระตุกเลย ผมขอเอาคำสั่งทั้งหมดไปเขียนสรุปไว้ในหัวข้อถัดไปเลยละกัน
🤔 LINQ ทำไรได้บ้าง ?
ความสามารถแค่ส่วนหลักๆของ LINQ ที่เราจะได้ใช้กันขอสรุปเป็นตารางไว้แบบนี้ละกัน
คำสั่งที่เอาไว้ทำงานกับ collection
กลุ่มนี้ทั้งหมดเป็น Deferred Execution - คืออะไรไปอ่านต่อได้จากด้านล่าง
คำสั่ง
ใช้สำหรับ
ผลลัพท์
Where
กรองข้อมูล
Collection
Select
เลือก
Collection
Distinct
ตัดตัวซ้ำ
Collection
Take
เอา
Collection
Skip
ข้าม
Collection
SkipWhile
ข้ามจนกว่า
Collection
TakeWhile
เอาจนกว่า
Collection
OrderBy
เรียงลำดับ น้อย-มาก
Collection
OrderByDescending
เรียงลำดับ มาก-น้อย
Collection
Reverse
เรียงลำดับกลับด้าน
Collection
Union
รวม 2 collection เข้าด้วยกัน
Collection
Intersect
เอาเฉพาะตัวที่ซ้ำกันใน 2 collection
Collection
Except
ตัดตัวที่ซ้ำกับ collection อื่น
Collection
คำสั่งที่ได้ผลลัพท์กลับมาเลย
กลุ่มนี้ทั้งหมดเป็น Forcing Immediate Execution - คืออะไรไปอ่านต่อได้จากด้านล่าง
คำสั่ง
ใช้สำหรับ
ผลลัพท์
Count
นับว่ามีกี่ตัว
number
Sum
หาผลรวม
number
Min
หาค่าน้อยสุด
number
Max
หาค่ามากสุด
number
Average
หาค่าเฉลี่ย
number
First
เอาข้อมูลตัวแรก
T
FirstOrDefault
เอาข้อมูลตัวแรก ถ้าไม่เจอขอ default
T หรือ default
Any
ดูว่ามีซักตัวไหมที่ตรงเงื่อนไข
bool
All
ทุกตัวตรงเงื่อนไขหรือไม่
bool
Contains
ใน collection มีตัวนี้หรือเปล่า
bool
คำสั่งในการแปลง collection
กลุ่มนี้ทั้งหมดเป็น Forcing Immediate Execution - คืออะไรไปอ่านต่อได้จากด้านล่าง
คำสั่ง
ใช้สำหรับ
ผลลัพท์
ToArray
แปลงเป็น Array<T>
Array<T>
ToList
แปลงเป็น List<T>
List<T>
ToDictionary
แปลงเป็น Dictionary<K, V>
Dictionary<K, V>
💡 Deferred vs Immediate
จากที่เคยอธิบายไปว่า LINQ มีการสั่ง execution ทั้งหมด 2 รูปแบบนั่นคือ Deferred Execution และ Forcing Immediate Execution ซึ่งทั้งสองตัวนี้แตกต่างกันสิ้นเชิง เพราะมันเกิดมาจาก 2 แนวคิดในการเขียนโค้ดนั่นเอง
ซึ่งการทำงานของ LINQ มันจะได้ผลลัพท์กลับมาทั้งหมด 2 แบบ โดยแต่ละแบบทำงานกันแบบนี้
🔥 Forcing Immediate Execution
เป็นแบบที่เราคุ้นเคยกันที่สุด นั่นคือเป็นการสั่งออกไปแล้วได้ผลลัพท์กลับมาทันทีนั่นเอง (ดูกลุ่มคำสั่งนี้ได้จากตารางด้านบน) เช่น โค้ดด้านล่างนี้ เป็นการหาค่าสูงสุดของข้อมูลใน collection
ผลลัพท์ result = 7
🔥 Deferred Execution
เป็นคำสั่งที่จะไม่ทำงานจนกว่าจะผ่านจุดที่เกิด Query Execution ขึ้นเท่านั้น ซึ่งคำสั่งตระกูลนี้จะได้ผลลัพท์กลับมาเป็น collection ของ IEnumerable<T> นั่นเอง (ดูกลุ่มคำสั่งนี้ได้จากตารางด้านบน) เช่นโค้ดตัวอย่างจะทำการดึงค่าเฉพาะเลขคู่ออกมาเท่านั้น แต่ผมจะเพิ่มว่าทุกๆ loop มันจะมีตัวนับเลขถูกเพิ่มค่าเข้าไปเรื่อยๆ ตามนี้
คำถามคือ runner มีค่าเป็นเท่าไหร่ถ้าผม run โค้ดเพียงเท่านี้เป๊ะๆเลย ?
เฉลย runner จะมีค่าเป็น 0 ครับ
เพราะคำสั่งในกลุ่มนี้มันเป็นการจำว่ามันจะต้องไปทำอะไรกับ data source อย่างเดียวเท่านั้น มันจะไม่ดำเนินการอะไรเลย จนกว่ามันจะผ่าน Query Execution ซักตัวนั่นเอง
จากที่ว่ามาผมก็เลยเพิ่ม query execution แบบง่ายๆเข้าไปนั่นคือ foreach แบบโง่ๆเลยตามนี้
คำถามคือ ในบรรทัดที่ 3 กับบรรทัดที่ 7 มันจะโชว์เลขอะไรออกมา ?
เฉลย Before Query Execution, Runner: 0 After Query Execution, Runner: 3
สาเหตุที่บรรทัดที่ 3 มันโชว์เลข 0 เพราะค่า query มันยังไม่ผ่าน Query Execution นั่นเอง ส่วนบรรทัดที่ 7 ที่มีนโชว์เลข 3 เพราะมันผ่าน Query Execution แล้วนั่นเองมันเลยไปเพิ่มค่า runner ยังไงล่ะ
🔥 Deferred Execution + Forcing Immediate Execution
ถ้าในกรณีที่เราเขียนคำสั่งเป็น Deferred Execution แต่เราจบด้วย Forcing Immediate Execution แล้วล่ะก็ มันจะกลายเป็น Forcing Immediate Execution โดยทันที เช่นโค้ดเดิมด้านบน ผมเอามาเขียนให้มันจบโดยใช้คำสั่ง .Count() ซึ่งเป็นคำสั่งของ forcing immediate execution ผมจะได้ผลลัพท์ออกมาแบบนี้
ผลลัพท์ Runner: 3
💡 Streaming vs Non-Streaming
ทุกๆครั้งที่เราไปดึงข้อมูลมาจาก data source เพื่อมาทำการประมวลผล มันจะมีการดึงข้อมูลมา 2 รูปแบบคือ
🔥 Streaming
เป็นการดึงข้อมูลมาแบบต่อเนื่อง โดยมันจะค่อยๆอ่านข้อมูลจาก data source มาทำการประมวลผลเรื่อยๆ ไม่ได้ดึงมาตูมเดียวทั้งหมดนั่นเอง ซึ่งเจ้าตัวนี้จะเหมาะใช้กับการทำงานกับ data source ที่มีขนาดใหญ่ๆ
🔥 Non-Streaming
เป็นการดึงข้อมูลมาตูมเดียวจบ แล้วทำการประมวลผลเลย
🤔 มีไรที่ควรรู้อีกไหม ?
เรื่องพื้นฐานสุดท้ายที่นึกออกละ คำสั่งพวก LINQ ทั้งหลายมันเป็น Extension Method ที่อยู่ใน namespace System.Linq; ดังนั้นมันหมายความว่าคำสั่ง LINQ มันเชื่อมกันได้ เช่น ผมมี Data Source เป็นตัวเลข 1-100 ตามนี้
แล้วผมต้องการข้อมูล 4 กลุ่มตามนี้
กลุ่มเลขคู่
กลุ่มเลขคู่ที่ 5 หารลงตัว
กลุ่มเลขคู่ที่ 7 หารลงตัว
กลุ่มเลขคู่ที่ 5 และ 7 หารลงตัว
เราก็จะสามารถใช้ความสามารถในการเชื่อมกันออกมาแบบนี้ได้
จะเห็นว่ากลุ่มที่เป็นเลขคู่จะใช้เป็นตัวตั้งต้นตัวแรก แล้วที่เหลือจะเอาผลลัพท์ของตัวแรกมาใช้ต่อเรื่อยๆได้ หรือในข้อ 4 เราจะเขียน Chain กันในรูปแบบนี้ก็ได้เหมือนกัน
🎯 บทสรุป
LINQ เป็นมหากาพย์ตัวนึงที่ดูเหมือนว่ามันจะเยอะมาก แต่ถ้าเราเข้าใจมันทั้งหมดแล้วเราจะพบว่า มันไม่มีอะไรเลย และไม่ต้องไปนั่งไล่จำอะไรเลย ขอแค่รู้หัวใจหลัก 3 เรื่องของมันก็พอ Data Source Query Query Execution เท่านั้นเอง เพียงเท่านี้โค้ดของเราก็จะกระชับและทรงพลังมาก เพราะมันสามารถไปเชื่อมใช้งานกับสิ่งต่างๆได้อีกเยอะเลย เช่น ทำ Query Database ทำงานร่วมกับ Entity Framework หรือแม้กระทั่งการทำงานกับ Reactive เช่น Reactive Extension (Rx) ก็ยังได้ คือจริงๆมันสารพัดประโยชน์มากจริงๆนะเจ้าตัวนี้ จนแทบจะเรียกว่าใครเขียน C# หากินเป็นอาชีพไม่รู้ไม่ได้
Last updated
Was this helpful?