緣起
前一陣子參與的專案裡, 用了不少 LINQ 的語法, 但沒有時間細推其原埋.其中, 常用到 GroupBy 的語法, 作各群組的 加熜, 平均, 最大值, 最小值, 計數 ... 等.
本文會採用 Query Syntax 及 Method Syntax 並陳的方式, 進行 GroupBy 的說明. 同時, 以 LINQPad 展示其執行結果, 會有助於了解 GroupBy 在作什麼.
註: 關於 Query Syntax, Method Syntax, LINQPad, 可以參考筆者的這篇文章.
完整程式範例, 筆者放在 GitHub, 請由此下載.
程式範例
以下範例, 均取自 MSDN, 加上一些修改; 主要是補上 Query Syntax 的部份.
範例一
該範例係將寵物依年紀作分組, 但放入 Group 的元素是 Pet.Name 這個屬性 (其型別為 string).
筆者加上了 Query Syntax 的語法, 比較容易瞭解其意義
筆者加上了 Query Syntax 的語法, 比較容易瞭解其意義
class Pet { public string Name { get; set; } public int Age { get; set; } } public static void GroupByEx1() { // Create a list of pets. List<Pet> pets = new List<Pet>{ new Pet { Name="Barley", Age=8 }, new Pet { Name="Boots", Age=4 }, new Pet { Name="Whiskers", Age=1 }, new Pet { Name="Daisy", Age=4 } }; // Group the pets using Age as the key value // and selecting only the pet's Name for each value. // Query Syntax // ----------------- //IEnumerable<IGrouping<int, string>> query1 = var query1 = from pet in pets group pet.Name by pet.Age; //Method Syntax // ----------------- IEnumerable<IGrouping<int, string>> query2 = pets.GroupBy(pet => pet.Age, pet => pet.Name); // Iterate over each IGrouping in the collection. Console.WriteLine("===== Query Syntax ====="); foreach (IGrouping<int, string> petGroup in query1) { // Print the key value of the IGrouping. Console.WriteLine(petGroup.Key); // Iterate over each value in the // IGrouping and print the value. foreach (string name in petGroup) Console.WriteLine(" {0}", name); } Console.WriteLine("===== Method Syntax ====="); foreach (IGrouping<int, string> petGroup in query2) { // Print the key value of the IGrouping. Console.WriteLine(petGroup.Key); // Iterate over each value in the // IGrouping and print the value. foreach (string name in petGroup) Console.WriteLine(" {0}", name); } //// for LINQPad //// ------------------ //query1.Dump(); //query2.Dump(); /* This code produces the following output: ===== Query Syntax ===== 8 Barley 4 Boots Daisy 1 Whiskers ===== Method Syntax ===== 8 Barley 4 Boots Daisy 1 Whiskers */ }
以下為採用 LINQPad 的輸出結果: 共有3個群組 ...
第1個群組: Key = 8, 其對應的元素為 IGrouping<int, string>, 這裡的 int 就是 Key, 以白話來看, 就是含有 {"Barley" } 這個物件
第2個群組: Key = 4, 其對應的元素為 IGrouping<int, string>, 這裡的 int 就是 Key, 以白話來看, 就是含有 { "Boots" } 及 { "Daisy"} 這2個物件
第3個群組: Key = 1, 其對應的元素為 IGrouping<int, string>, 這裡的 int 就是 Key, 以白話來看, 就是含有 { "Whiskers" } 這個物件
註: select projection 出來的只有 Name 這個欄位, 所以內部存放 string 的資料型別
追蹤源碼發現, 最後會有一個實作 IGrouping 的類別 Grouping, 如下; 該類別實作了 IGrouping<TKey, TElement>, IList<TElement>, 所以內部有一個 TElement[] elements 的集合. 亦即可以對照到上圖的結構.
第1個群組: Key = 8, 其對應的元素為 IGrouping<int, string>, 這裡的 int 就是 Key, 以白話來看, 就是含有 {"Barley" } 這個物件
第2個群組: Key = 4, 其對應的元素為 IGrouping<int, string>, 這裡的 int 就是 Key, 以白話來看, 就是含有 { "Boots" } 及 { "Daisy"} 這2個物件
第3個群組: Key = 1, 其對應的元素為 IGrouping<int, string>, 這裡的 int 就是 Key, 以白話來看, 就是含有 { "Whiskers" } 這個物件
註: select projection 出來的只有 Name 這個欄位, 所以內部存放 string 的資料型別
LINQ GroupBy 的輸出結果 (Example01) |
追蹤源碼發現, 最後會有一個實作 IGrouping 的類別 Grouping, 如下; 該類別實作了 IGrouping<TKey, TElement>, IList<TElement>, 所以內部有一個 TElement[] elements 的集合. 亦即可以對照到上圖的結構.
internal class Grouping : IGrouping<TKey, TElement>, IList<TElement> { internal TKey key; internal int hashCode; internal TElement[] elements; internal int count; internal Grouping hashNext; internal Grouping next; internal void Add(TElement element) { if (elements.Length == count) Array.Resize(ref elements, checked(count * 2)); elements[count] = element; count++; } public IEnumerator<TElement> GetEnumerator() { for (int i = 0; i < count; i++) yield return elements[i]; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } // DDB195907: implement IGrouping<>.Key implicitly // so that WPF binding works on this property. public TKey Key { get { return key; } } int ICollection<TElement>.Count { get { return count; } } bool ICollection<TElement>.IsReadOnly { get { return true; } } void ICollection<TElement>.Add(TElement item) { throw Error.NotSupported(); } void ICollection<TElement>.Clear() { throw Error.NotSupported(); } bool ICollection<TElement>.Contains(TElement item) { return Array.IndexOf(elements, item, 0, count) >= 0; } void ICollection<TElement>.CopyTo(TElement[] array, int arrayIndex) { Array.Copy(elements, 0, array, arrayIndex, count); } bool ICollection<TElement>.Remove(TElement item) { throw Error.NotSupported(); } int IList<TElement>.IndexOf(TElement item) { return Array.IndexOf(elements, item, 0, count); } void IList<TElement>.Insert(int index, TElement item) { throw Error.NotSupported(); } void IList<TElement>.RemoveAt(int index) { throw Error.NotSupported(); } TElement IList<TElement>.this[int index] { get { if (index < 0 || index >= count) throw Error.ArgumentOutOfRange("index"); return elements[index]; } set { throw Error.NotSupported(); } } }
範例二
該範例係將寵物依年紀作分組, 但放入 Group 的元素是 Pet 這個類別.
以下為採用 LINQPad 的輸出結果, 共有3個群組 ...
第1個群組: Key = 8, 其對應的元素為 IGrouping<int, Pet>, 這裡的 int 就是 Key, 以白話來看, 就是含有 {8, "Barley" } 這個 Pet 物件
第2個群組: Key = 4, 其對應的元素為 IGrouping<int, Pet>, 這裡的 int 就是 Key, 以白話來看, 就是含有 { 4, "Boots" } 及 { 4, "Daisy"} 這2個 Pet 物件
第3個群組: Key = 1, 其對應的元素為 IGrouping<int, Pet>, 這裡的 int 就是 Key, 以白話來看, 就是含有 { 1, "Whiskers" } 這個 Pet 物件
註: select projection 出來的 Pet 這個類別, 所以內部存放 Pet 的資料型別
public static void GroupByEx2() { // Create a list of pets. List<Pet> pets = new List<Pet>{ new Pet { Name="Barley", Age=8 }, new Pet { Name="Boots", Age=4 }, new Pet { Name="Whiskers", Age=1 }, new Pet { Name="Daisy", Age=4 } }; // Group the pets using Age as the key value // and selecting only the pet's Name for each value. // Query Syntax // ----------------- //IEnumerable<IGrouping<int, Pet>> query1 = var query1 = from pet in pets group pet by pet.Age; //Method Syntax // ----------------- IEnumerable<IGrouping<int, Pet>> query2 = pets.GroupBy(pet => pet.Age, pet => pet); // Iterate over each IGrouping in the collection. Console.WriteLine("===== Query Syntax ====="); foreach (IGrouping<int, Pet> petGroup in query1) { // Print the key value of the IGrouping. Console.WriteLine(petGroup.Key); // Iterate over each value in the // IGrouping and print the value. foreach (var item in petGroup) Console.WriteLine(" {0} : {1}", item.Name, item.Age ) ; } Console.WriteLine("===== Method Syntax ====="); foreach (IGrouping<int, Pet> petGroup in query2) { // Print the key value of the IGrouping. Console.WriteLine(petGroup.Key); // Iterate over each value in the // IGrouping and print the value. foreach (var item in petGroup) Console.WriteLine(" {0} : {1}", item.Name, item.Age); } //// for LINQPad //// ------------------ //query1.Dump(); //query2.Dump(); /* This code produces the following output: ===== Query Syntax ===== 8 Barley : 8 4 Boots : 4 Daisy : 4 1 Whiskers : 1 ===== Method Syntax ===== 8 Barley : 8 4 Boots : 4 Daisy : 4 1 Whiskers : 1 */ }
以下為採用 LINQPad 的輸出結果, 共有3個群組 ...
第1個群組: Key = 8, 其對應的元素為 IGrouping<int, Pet>, 這裡的 int 就是 Key, 以白話來看, 就是含有 {8, "Barley" } 這個 Pet 物件
第2個群組: Key = 4, 其對應的元素為 IGrouping<int, Pet>, 這裡的 int 就是 Key, 以白話來看, 就是含有 { 4, "Boots" } 及 { 4, "Daisy"} 這2個 Pet 物件
第3個群組: Key = 1, 其對應的元素為 IGrouping<int, Pet>, 這裡的 int 就是 Key, 以白話來看, 就是含有 { 1, "Whiskers" } 這個 Pet 物件
註: select projection 出來的 Pet 這個類別, 所以內部存放 Pet 的資料型別
LINQ GroupBy 的輸出結果 (Example02) |
範例三
該範例係將寵物依年紀作分組 (注意: Age 的部份改為 double 型別), 並取得其 Count, Average, Min, Max 等資料
以下為採用 LINQPad 的輸出結果:
class Pet2 { public string Name { get; set; } public double Age { get; set; } } public static void GroupByEx3() { // Create a list of pets. List<Pet2> petsList = new List<Pet2>{ new Pet2 { Name="Barley", Age=8.3 }, new Pet2 { Name="Boots", Age=4.9 }, new Pet2 { Name="Whiskers", Age=1.5 }, new Pet2 { Name="Daisy", Age=4.3 } }; // Group Pet objects by the Math.Floor of their age. // Then project an anonymous type from each group // that consists of the key, the count of the group's // elements, and the minimum and maximum age in the group. // Query Syntax // ----------------- var query1 = from p in petsList group p by Math.Floor(p.Age) into pets select new { Key = pets.Key, Count = pets.Count(), Min = pets.Min(pet => pet.Age), Max = pets.Max(pet => pet.Age), DetailList = new List<Pet2>(pets) }; // Method Syntax // ----------------- var query2 = petsList.GroupBy( pet => Math.Floor(pet.Age), (age, pets) => new { Key = age, Count = pets.Count(), Min = pets.Min(pet => pet.Age), Max = pets.Max(pet => pet.Age), DetailList = new List<Pet2>(pets) }); // Iterate over each anonymous type. Console.WriteLine(""); Console.WriteLine("===== Query Syntax ====="); foreach (var result in query1) { Console.WriteLine("Age group: " + result.Key); Console.WriteLine("Number of pets in this age group: " + result.Count); Console.WriteLine("Minimum age: " + result.Min); Console.WriteLine("Maximum age: " + result.Max); foreach (var item in result.DetailList) { Console.WriteLine(" Members: " + item.Name + item.Age); } } Console.WriteLine("===== Method Syntax ====="); foreach (var result in query2) { Console.WriteLine("Age group: " + result.Key); Console.WriteLine("Number of pets in this age group: " + result.Count); Console.WriteLine("Minimum age: " + result.Min); Console.WriteLine("Maximum age: " + result.Max); foreach (var item in result.DetailList) { Console.WriteLine(" Members: " + item.Name + item.Age); } } //// for LINQPad //// ------------------ //query1.Dump(); //query2.Dump(); /* This code produces the following output: ===== Query Syntax ===== Age group: 8 Number of pets in this age group: 1 Minimum age: 8.3 Maximum age: 8.3 Members: Barley8.3 Age group: 4 Number of pets in this age group: 2 Minimum age: 4.3 Maximum age: 4.9 Members: Boots4.9 Members: Daisy4.3 Age group: 1 Number of pets in this age group: 1 Minimum age: 1.5 Maximum age: 1.5 Members: Whiskers1.5 ===== Method Syntax ===== Age group: 8 Number of pets in this age group: 1 Minimum age: 8.3 Maximum age: 8.3 Members: Barley8.3 Age group: 4 Number of pets in this age group: 2 Minimum age: 4.3 Maximum age: 4.9 Members: Boots4.9 Members: Daisy4.3 Age group: 1 Number of pets in this age group: 1 Minimum age: 1.5 Maximum age: 1.5 Members: Whiskers1.5 */ }
以下為採用 LINQPad 的輸出結果:
LINQ GroupBy 的輸出結果 (Example03) |
總結
本文針對了 C# LINQ GroupBy 的 Query Syntax 及 Method Syntax 的語法, 作了一些範例的呈現, 同時以 LINQPad 展示其輸出結構, 供有興趣者作參考.
參考文件
- MSDN
- 本文範例的主要參考對象
- [C#]LINQ–GroupBy 群組
- 該文章有提供由淺入深的一些 GroupBy 範例
沒有留言:
張貼留言