description |
---|
🤔 คำสั่งของ LINQ ที่ได้ใช้บ่อยๆมีไรบ้างนะ |
บทความนี้เป็นบทความที่แยกออกมาจากเรื่อง LINQ ซึ่งเป็นหนึ่งในคำสั่งเทพเจ้าของสาย .NET ซึ่งมันจะทำให้ developer ทำงานได้สบายลงแบบฝุดๆ ดังนั้นใครยังไม่รู้เรื่อง LINQ ให้กลับไปอ่านบทความนี้ก่อนเน่อ Saladpuk - LINQ 101
Where - เป็นการเลือกเอาเฉพาะข้อมูลที่เราสนใจออกมา เช่น มี data source เป็นเลข 1~100 แล้วต้องการเอาเฉพาะเลขที่ 5 และ 7 หารลงตัวออกมา ก็จะเขียนออกมาได้เป็นแบบนี้
var collection = Enumerable.Range(1, 100);
var qry = collection.Where(it => it % 5 == 0 && it % 7 == 0);
// ผลลัพท์: { 35, 70 }
Select - เป็นการเลือกว่า data source ที่เราไปดึงข้อมูลมา เราจะดัดแปลงแก้ไข หรือ เลือกเอาเฉพาะข้อมูลบางส่วนออกมาใช้
เช่นมี collection เป็นเลข 1~5 ตอนที่เราจะเอามาทำงานด้วยเราจะแก้ให้มันถูก คูณด้วย 10 ก่อนค่อยเอามาใช้งาน ก็จะเขียนได้แบบนี้
var collection = new int[] { 1, 2, 3, 4, 5 };
var qry = collection.Select(it => it * 10);
// ผลลัพท์: { 10, 20, 30, 40, 50 }
หรือ จะให้มันเปลี่ยนเป็นข้อมูลอีกประเภทนึงเลยก็ได้
var qry = collection.Select(it => new Student{ Id = it });
public class Student
{
public int Id { get; set; }
}
ส่วนถ้าข้อมูลใน data source มันวุ่นวายเกินไป เราก็สามารถเลือกแค่บางส่วนของมันมาใช้ก็ได้นะ เช่น เราอยากได้แค่ Name ที่อยู่ใน collection มาใช้เท่านั้น ก็เขียนเป็นแบบนี้ได้
var collection = new[]
{
new { Id = 1, Name = "A", Age = 10 },
new { Id = 2, Name = "B", Age = 15 },
new { Id = 3, Name = "C", Age = 20 },
};
var qry = collection.Select(it => it.Name);
// ผลลัพท์: { "A", "B", "C" }
SelectMany - เป็นการเลือกเข้าไปถึงหน่วยย่อยของ collection ที่ซ้อนภายใน collection อีกทีนึง
var collection = new[]
{
new [] { 1, 2, 3, 4 },
new [] { 5, 6, 7, 8 },
};
var qry = collection.SelectMany(it => it);
{% hint style="info" %}
แนะนำให้อ่าน
คำสั่ง SelectMany สำหรับคนที่พึ่งหัดใช้ LINQ อาจจะ งงๆ หน่อยแต่ถ้าเราได้ทำงานร่วมกับพวก collection ซ้อน collection แล้วล่ะก็ควรจะทำความเข้าใจมันเอาไว้นะ ซึ่งอ่านได้จากลิงค์นี้เลย
Microsoft document - Projection Operations
{% endhint %}
ในบางทีเราอยากจะทำงานกับข้อมูลแค่ตัวใดตัวหนึ่งหรือส่วนหนึ่งที่อยู่ใน collection เราก็สามารถใช้คำสั่งที่อยู่ด้านล่างได้ เช่น เรามี data source ที่มีข้อมูลเป็นเลข 1~100
var collection = Enumerable.Range(1, 100);
First - เอาเฉพาะตัวแรกออกมา
var result = collection.First();
// ผลลัพท์: { 1 }
FirstOrDefault - เหมือนกับ First ทุกประการ ต่างกันแค่ถ้ามันดึงค่าออกมาไม่ได้มันจะส่งค่า default ของ data type นั้นๆกลับมา
var result = collection.FirstOrDefault();
// ผลลัพท์: { 1 }
Last - เอาเฉพาะตัวสุดท้ายออกมา
var result = collection.Last();
// ผลลัพท์: { 100 }
LastOrDefault - เหมือนกับ Last ทุกประการ ต่างกันแค่ถ้ามันดึงค่าออกมาไม่ได้มันจะส่งค่า default ของ data type นั้นๆกลับมา
var result = collection.LastOrDefault();
// ผลลัพท์: { 100 }
ElementAt - เป็นการดึงค่าที่อยู่ใน index ที่กำหนดออกมา
var result = collection.ElementAt(3);
// ผลลัพท์: { 4 }
ElementAtOrDefault - เหมือนกับ ElementAt ทุกประการ ต่างกันแค่ถ้ามันดึงค่าออกมาไม่ได้มันจะส่งค่า default ของ data type นั้นๆกลับมา
var result = collection.ElementAtOrDefault(9999);
// ผลลัพท์: { 0 }
{% hint style="danger" %}
อันตราย
ถ้า data source เป็น collection ว่าง แล้วไปใช้คำสั่งพวก First, Last, ElementAt มันจะทำให้เกิด Exception ได้ครับ ดังนั้นโดยปรกติผมจะแนะนำให้ใช้คำสั่ง FirstOrDefault, LastOrDefault, ElementAtOrDefault แทนมากกว่า เพราะค่า overhead ในการจัดการกับ error มันสูงกว่าครับ
{% endhint %}
เวลาที่เราทำงานกับ data source ปริมาณมากๆ เราสามารถที่จะทำการแบ่งข้อมูลออกเป็นส่วนๆ เพื่อให้ง่ายในการทำงานได้ เช่น มี collection ตัวเลข 1~100 อยู่ตามด้านล่าง
var collection = Enumerable.Range(1, 100);
Take - เป็นการสั่งให้ดึงข้อมูลจาก data source ออกมาเท่าที่เรากำหนดไว้ เช่น เราอยากดึงข้อมูลมาแค่ 5 ตัวแรกก่อน เราก็จะเขียนได้ว่า
var qry = collection.Take(5);
// ผลลัพท์: { 1, 2, 3, 4, 5 }
TakeLast - เหมือนกับ Take แต่จะดึงมาจากด้านหลังสุด เช่น อยากจะดึงข้อมูล 5 ตัวจากด้านหลังสุดออกมา
var qry = collection.TakeLast(5);
// ผลลัพท์: { 96, 97, 98, 99, 100 }
TakeWhile - เป็นการสั่งให้มันดึงข้อมูลจาก data source ออกมาเรื่อยๆจนกว่าจะเจอตัวแรกที่ทำให้เงื่อนไขไม่เป็นจริง เช่น ให้ดึงมาเรื่อยๆถ้าเลขที่ดึงมามันยังน้อยกว่า 8
var qry = collection.TakeWhile(it => it < 8);
// ผลลัพท์: { 1, 2, 3, 4, 5, 6, 7 }
Skip - สั่งให้ข้ามข้อมูลเท่ากับที่เรากำหนด เช่น เราต้องการข้ามข้อมูล 4 ตัวแรกไป
var qry = collection.Skip(4);
// ผลลัพท์: { 5, 6, 7 ... 100 }
SkipLast - เหมือนกับ Skip ต่างกันแค่มันจะข้ามเฉพาะตัวด้านหลังสุด เช่น อยากจะข้ามข้อมูล 4 ตัวสุดท้ายไป
var qry = collection.SkipLast(4);
// ผลลัพท์: { 1, 2, 3 ... 96 }
SkipWhile - เป็นการสั่งให้มันข้ามข้อมูลไปเรื่อยๆ ถ้าเงื่อนไขยังเป็นจริงอยู่ และจะหยุดข้ามเมื่อเจอข้อมูลตัวแรกที่ไม่ตรงเงื่อนไข เช่น อยากจะข้ามไปเรื่อยๆจนกว่าจะเจอตัวแรกที่มากกว่า 50
var qry = collection.SkipWhile(it => it < 50);
// ผลลัพท์: { 50, 51, 52 ... 100 }
เราสามารถทำงานกับ data source ที่เป็น 2 กลุ่มให้มาทำงานร่วมกันได้ 3 แบบคือ
เช่นเรามีข้อมูลกลุ่ม a กับกลุ่ม b เป็นแบบนี้
var a = new[] { 1, 2, 3, 4, 5 };
var b = new[] { 4, 5, 6, 7, 8 };
Intersect - ตามรูปเลยคือ เอาเฉพาะที่มันเหมือนกันออกมา
var intersect = a.Intersect(b);
// ผลลัพท์: { 4, 5 }
Union - ตามรูปเลยคือ เอาทั้งสองกลุ่มมารวมกัน
var union = a.Union(b);
// ผลลัพท์: { 1, 2, 3, 4, 5, 6, 7, 8 }
Except - ตามรูปเลยคือ เอาเฉพาะของที่ไม่ซ้ำกับอีกกลุ่มออกมา
var except = a.Except(b);
// ผลลัพท์: { 1, 2, 3 }
Distinct - เป็นการตัดตัวซ้ำทิ้ง
var collection = new[] { 1, 1, 2, 2, 3, 4, 4, 3 };
var qry = collection.Distinct();
// ผลลัพท์: { 1, 2, 3, 4 }
การเรียงลำดับเราทำได้ 3 แบบ น้อยไปมาก
มากไปน้อย
และ กลับด้านข้อมูล
เช่นเรามี data source เป็นแบบนี้
var collection = new int[] { 7, 5, 2, 6, 4, 1, 3 };
OrderBy - เรียงลำดับจากน้อยไปมาก หรือถ้าเป็นตัวอักษรจะเป็นการเรียงจาก a~Z
var ascending = collection.OrderBy(it => it);
// ผลลัพท์: { 1, 2, 3, 4, 5, 6, 7 }
OrderByDescending - เรียงลำดับจากมากไปน้อย
var descending = collection.OrderByDescending(it => it);
// ผลลัพท์: { 7, 6, 5, 4, 3, 2, 1 }
Reverse - เรียงลำดับแบบกลับด้าน ขวาไปซ้าย แทน
var reverse = collection.Reverse();
// ผลลัพท์: { 3, 1, 4, 6, 2, 5, 7 }
ThenBy และ ThenByDescending - แต่ถ้าข้อมูลมีความซับซ้อนมากขึ้น เราสามารถกำหนดความสำคัญในการเรียงลำดับได้ด้วย เช่น เรียงลำดับจากคะแนนน้อยไปมาก แต่ถ้าคะแนนเท่ากันให้เรียงจากชื่อตามลำดับตัวอักษรก็จะเขียนแบบนี้ได้
var collection = new[]
{
new { Score = 7, Name = "B" },
new { Score = 3, Name = "A" },
new { Score = 7, Name = "A" },
new { Score = 4, Name = "A" },
new { Score = 3, Name = "C" },
};
// น้อยไปมาก และ ตามลำดับตัวอักษร
var ascending = collection
.OrderBy(it => it.Score)
.ThenBy(it => it.Name);
// มากไปน้อย และ ตามลำดับตัวอักษร
var descending = collection
.OrderByDescending(it => it)
.ThenBy(it => it.Name);
เราสามารถหาผลลัพท์จากข้อมูลใน collection ได้เช่น มีบางตัวไหม? หรือ ทุกตัวเป็นแบบนี้ไหม? อาจจะฟังแล้ว งงๆ ไปดูตัวอย่างเลยดีกว่า โดยสมมุติว่าผมมี data source เป็นแบบนี้
var collection = new int[] { 2, 4, 6, 8, 10 };
Any - ถามว่ามีซักตัวไหมที่เป็นแบบนี้ เช่น อยากรู้ว่ามีซักตัวไหมใน collection ที่มีค่ามากกว่า 9 ก็สามารถเขียนเป็น
var any = collection.Any(it => it > 9); // true
All - ถามว่าทุกตัวเป็นแบบนี้หรือเปล่า เช่น อยากเช็คว่าทุกตัวใน collection มากกว่า 5 หรือเปล่า
var all = collection.All(it => it > 5); // false
Contains - ถามว่าภายในนั้นมีตัวนี้อยู่หรือเปล่า เช่น collection นั้นมีเลข 8 อยู่ในนั้นหรือเปล่า
var contain = collection.Contains(8); // true
GroupBy - สั่งให้มันจัดกลุ่มของข้อมูลได้ เช่น มี collection ของคนหลายๆคน แล้วเราอยากให้จัดกลุ่มคนตามอายุ เราก็จะเขียนได้ว่า
var collection = new[]
{
new { Name = "A", Age = 15 },
new { Name = "B", Age = 7 },
new { Name = "C", Age = 7 },
new { Name = "D", Age = 15 },
new { Name = "E", Age = 9 },
};
var qry = collection.GroupBy(it => it.Age);
/* ผลลัพท์
15
{ Name = A, Age = 15 }
{ Name = D, Age = 15 }
7
{ Name = B, Age = 7 }
{ Name = C, Age = 7 }
9
{ Name = E, Age = 9 }
*/
ถ้าเราต้องการสร้างข้อมูลที่เป็น collection ขึ้นมาแบบง่ายๆ เราก็สามารถใช้ LINQ ช่วยสร้างได้
Range - สร้างชุดตัวเลขออกมา เช่น อยากได้ collection ตัวเลขตั้งแต่ 1 ถึง 100
var qry = Enumerable.Range(1, 100);
// ผลลัพท์: { 1, 2, 3 ... 100 }
Empty - สร้าง collection ว่างออกมา เช่น เราอยากได้ collection ของตัวเลข แต่ไม่ต้องมีข้อมูลอะไรอยู่ข้างในนะ
var qry = Enumerable.Empty<int>();
// ผลลัพท์: { }
DefaultIfEmpty - ถ้าเราต้องไปทำงานกับ collection ตัวเลขซักตัว แต่ถ้า collection นั้นมันเป็นค่าว่าง เราจะกำหนดค่า 9 ให้มันไปใช้แทน
var collection = Enumerable.Empty<int>();
var qry = collection.DefaultIfEmpty(9);
// ผลลัพท์: { 9 }
Repeat - สร้างชุดข้อมูลซ้ำๆกันออกมา เช่น อยากได้ collection เลข 5 ซ้ำกัน 3 ตัว ก็เขียนแบบนี้ได้
var qry = Enumerable.Repeat(5, 3);
// ผลลัพท์: { 5, 5, 5 }
เราสามารถแปลง data source ของเราจาก data type นึงไปยังอีก data type นึงก็ได้นะ เช่น มีข้อมูล collection เลข 1~5 ตามนี้
var collection = new[] { 1, 2, 3, 4, 5 };
AsEnumerable - แปลงให้มันกลับมาเป็น IEnumerable<T> เอาไว้ช่วยแปลงจาก collection อะไรก็ตามให้กลับมาสู่ base class ของกลุ่ม collection
var qry = collection.AsEnumerable();
AsQueryable - แปลงให้คำสั่งทั้งหมดยังเป็นแค่ Query เท่านั้น ซึ่งใช้ได้ดีตอนที่ทำงานร่วมกับ database เพราะเราจะได้ส่งแต่คำสั่งไปประมวลผลที่ database เท่านั้นไม่ได้ส่งข้อมูลปริมาณมหาศาลกลับมาถล่มที่ client
var qry = collection.AsQueryable();
Cast - แปลงข้อมูลจาก data source ให้กลายเป็น data type ที่กำหนด
var collection = new[]
{
new Dog { Id = 1, OwnerName = "Saladpuk" },
};
IEnumerable<Animal> qry = collection.Cast<Animal>();
public class Animal
{
public int Id { get; set; }
}
public class Dog : Animal
{
public string OwnerName { get; set; }
}
OfType - เลือกเอาเฉพาะ data type ที่ตรงกับที่กำหนด
var collection = new object[]
{
new Dog { OwnerName = "Saladpuk" },
new Cat { IsFriendly = false },
};
var qry = collection.OfType<Dog>();
// ผลลัพท์: [ { OwnerName = 'Saladpuk' } ]
public class Dog
{
public string OwnerName { get; set; }
}
public class Cat
{
public bool IsFriendly { get; set; }
}
ToArray - แปลงให้ collection นั้นๆกลายเป็น Array
var qry = Enumerable.Range(1, 100);
int[] result = qry.ToArray();
ToList - แปลงให้ collection นั้นๆกลายเป็น List
var qry = Enumerable.Range(1, 100);
List<int> result = qry.ToList();
ToDictionary - แปลงให้ collection นั้นๆกลายเป็น Dictionary<K, V> เช่นทำการจัดกลุ่มว่าใครเรียนอยู่ห้องไหนบ้าง แล้วทำการไปสร้างเป็น dictionary
var collection = new[]
{
new { Id = 1, ClassRoom = "A", Name = "Saladpuk" },
new { Id = 2, ClassRoom = "B", Name = "Thaksin" },
new { Id = 3, ClassRoom = "C", Name = "Prayut" },
new { Id = 4, ClassRoom = "B", Name = "Yingluck" },
new { Id = 5, ClassRoom = "C", Name = "Abhisit" },
};
var result = collection
.GroupBy(it => it.ClassRoom)
.ToDictionary(it => it.Key, it => it);
/* ผลลัพท์
A
{ Id = 1, ClassRoom = A, Name = Saladpuk }
B
{ Id = 2, ClassRoom = B, Name = Thaksin }
{ Id = 4, ClassRoom = B, Name = Yingluck }
C
{ Id = 3, ClassRoom = C, Name = Prayut }
{ Id = 5, ClassRoom = C, Name = Abhisit }
*/
Concate - เป็นการเอา 2 collection มาต่อกันแบบดื้อๆเลย
var a = new[] { 1, 2, 3, 4, 5 };
var b = new[] { 4, 5, 6, 7, 8 };
var qry = a.Concat(b);
// ผลลัพท์: { 1, 2, 3, 4, 5, 4, 5, 6, 7, 8 }
เป็นกลุ่มคำสั่งที่ได้ผลลัพท์กลับมาเลย และเป็นการทำงานแบบ Imperative เช่นมี data source เป็นเลข 1~10 ตามนี้
var collection = Enumerable.Range(1, 10);
Sum - หาผลรวม
var result = collection.Sum();
// ผลลัพท์: 55
Average - หาค่าเฉลี่ย
var result = collection.Average();
// ผลลัพท์: 5.5
Max - หาค่าสูงสุด
var result = collection.Max();
// ผลลัพท์: 10
Min - หาค่าต่ำสุด
var result = collection.Min();
// ผลลัพท์: 1
Count - นับว่าภายใน data source มีข้อมูลอยู่ทั้งหมดเท่าไหร่
var result = collection.Count();
// ผลลัพท์: 10
Aggregate - นำข้อมูลทั้ง collection มาดำเนินการแบบต่อเนื่องกัน
var result = collection.Aggregate((a, b) => a * b);
// ผลลัพท์: 3628800
{% hint style="info" %}
แนะนำให้อ่าน
คำสั่ง Aggregate ถ้าเราใช้เป็นจริงๆมันทรงพลังมากเลยนะ ลองศึกษาเพิ่มเติมได้จากลิงค์นี้เบย
Microsoft document - Aggregation
{% endhint %}
จากคำสั่งทั้งหมดที่เขียนมาเป็นตัวอย่าง สุดท้ายการทำงานของมันก็จะตกมาอยู่ในกลุ่ม 3 กลุ่มนั่นเองคือ
- ทำงานโดยทันที Immediate
- ไม่ทำงานจนกว่าจะเรียกใช้ Deferred
- ดึงข้อมูลทั้งหมดมาก่อนค่อยทำงาน Non-Streaming
- ค่อยทะยอยดึงข้อมูลมาเรื่อยๆ Streaming