緣起
最近在研讀 ASP.NET MVC 5 : 網站開發美學 Ch.03 時, 發現對於 SelectMany() 這個函式仍然不是很瞭解, 於是查了 MSDN 上的範例, 再加上配合 .NET Framework 源碼 一併進行 debug, 終於有了一些概念; 故撰寫本文, 以進行整理.完整範例, 請由此下載.
過程
說明相關程式檔案建立過程, 最後的版本, 可自行下載本範例. 在範例程式碼裡, 都有作了細節的說明, 可以參考.步驟1
自行建立 MyEnumerable 類別, 將 .NET Framework 源碼中 SelectMany() 相關擴充方法收錄至該類別, 以供除錯時, 得以明確看到程式執行過程.總共有 3 個 overloading 的擴充方法,
(1) 修改方法名稱, 在每個方法加上 My, 以與 .NET Framework 的源碼的原有方法作區隔
(2) 將有 Error.xxxx 的程式段註解掉,
註: .NET Framework 源碼 連結: ( http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,8f3471331178bcb0 )
Case 1:
SelectMany() 的參數說明:// 參數1: this IEnumerable<TSource> source --> 即 IEnumerable<PetOwner>
// 參數2: Func<TSource, IEnumerable<TResult>> selector --> 即 Func<PetOwner, IEnumerable<String>>
// source: PetOwner[] 陣列 (共 3 個元素)
// selector: petOwner => petOwner.Pets, 其中, petOwner 為 PetOwner 類別的物件實體
public static IEnumerable<TResult> MySelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
其實由 SelectMany() 實在看不太出來究竟回傳什麼, 由 SelectManyIterator() 來看, 就清楚多了.
foreach (TResult subElement in selector(element))
{
yield return subElement;
}
[註]: selector 為一個 delegate: Func<TSource, IEnumerable<TResult>>,
其傳入參數為 (element); 其實作為呼叫端的 petOwner => petOwner.Pets
#region CASE 1 的 SelectMany() 源碼
//參數1: this IEnumerable<TSource> source --> 即 IEnumerable<PetOwner> //參數2: Func<TSource, IEnumerable<TResult>> selector --> 即 Func<PetOwner, IEnumerable<String>> //source: PetOwner[] 陣列 (共 3 個元素) //selector: petOwner => petOwner.Pets, 其中, petOwner 為 PetOwner 類別的物件實體 public static IEnumerable<TResult> MySelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector) { //if (source == null) throw Error.ArgumentNull("source"); //if (selector == null) throw Error.ArgumentNull("selector"); return MySelectManyIterator<TSource, TResult>(source, selector); } static IEnumerable<TResult> MySelectManyIterator<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector) { //source: PetOwner[] 陣列 (共 3 個元素) //element: PetOwner[] 陣列裡的每個元素, 即 PetOwner 的物件實體 //selector: petOwner => petOwner.Pets, 其中, petOwner 即為 element foreach (TSource element in source) { //執行 selector(element) 之後, 會產出 petOwner.Pets 回傳 foreach (TResult subElement in selector(element)) { yield return subElement; } } } #endregion
Case 2:
SelectMany() 的參數說明://參數1: this IEnumerable<TSource> source --> 即 IEnumerable<PetOwner>
//參數2: Func<TSource, int, IEnumerable<TResult>> selector --> 即 Func<PetOwner, int, IEnumerable<String>>
//source: PetOwner[] 陣列 (共 4 個元素)
//selector: (petOwner, index) => petOwner.Pets.Select(pet => index + pet), 其中, petOwner 為 PetOwner 類別的物件實體, index 為 int
public static IEnumerable<TResult> MySelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TResult>> selector)
foreach (TResult subElement in selector(element, index))
{
yield return subElement;
}
[註]: selector 為一個 delegate: Func<TSource, int, IEnumerable<TResult>>
其參數為 (element, index); 其實作為呼叫端的 (petOwner, index) => petOwner.Pets.Select(pet => index + pet)
#region CASE 2 的 SelectMany() 源碼 //參數1: this IEnumerable<TSource> source --> 即 IEnumerable<PetOwner> //參數2: Func<TSource, int, IEnumerable<TResult>> selector --> 即 Func<PetOwner, int, IEnumerable<String>> //source: PetOwner[] 陣列 (共 4 個元素) //selector: (petOwner, index) => petOwner.Pets.Select(pet => index + pet), 其中, petOwner 為 PetOwner 類別的物件實體, index 為 int public static IEnumerable<TResult> MySelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TResult>> selector) { // if (source == null) throw Error.ArgumentNull("source"); // if (selector == null) throw Error.ArgumentNull("selector"); return MySelectManyIterator<TSource, TResult>(source, selector); } static IEnumerable<TResult> MySelectManyIterator<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TResult>> selector) { //source: PetOwner[] 陣列 (共 4 個元素) //element: PetOwner[] 陣列裡的每個元素, 即 PetOwner 的物件實體 //selector: (petOwner, index) => petOwner.Pets.Select(pet => index + pet); 其中, petOwner 即為 element, index 為 int // 註: index 代表陣列元素的位置, 由 0 起算 int index = -1; foreach (TSource element in source) { checked { index++; } foreach (TResult subElement in selector(element, index)) { yield return subElement; } } } #endregion
Case 3:
SelectMany() 的參數說明://參數1: this IEnumerable<TSource> source --> 即 IEnumerable<PetOwner>
//參數2: Func<TSource, IEnumerable<TCollection>> collectionSelector --> 即 Func<PetOwner, IEnumerable<String>>
//參數3: Func<TSource, TCollection, TResult> resultSelector --> 即 Func<PetOwner, String, IEnumerable<String>>
//source: PetOwner[] 陣列 (共 4 個元素)
//collectionSelector: petOwner => petOwner.Pets
//resultSelector: (petOwner, petName) => new { petOwner, petName }
public static IEnumerable<TResult> MySelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)
//把 element 傳入 collectionSelector 執行
//petOwner => petOwner.Pets ---> element => element.Pets
foreach (TCollection subElement in collectionSelector(element))
{
//把 element, subElement 傳入 resultSelector 執行
//(petOwner, petName) => new { petOwner, petName } ---> (element, subElement) => new { element, subElement }
yield return resultSelector(element, subElement);
}
[註]:
(1) collectionSelector 為一個 delegate: Func<TSource, IEnumerable<TCollection>>
其參數為 (element); 其實作為呼叫端的 petOwner => petOwner.Pets
(2) resultSelector 為一個 delegate: Func<TSource, TCollection, TResult>
其參數為 (element, subElement), 其實作為呼叫端的 (petOwner, petName) => new { petOwner, petName }
#region CASE 3 的 SelectMany() 源碼 //參數1: this IEnumerable<TSource> source --> 即 IEnumerable<PetOwner> //參數2: Func<TSource, IEnumerable<TCollection>> collectionSelector --> 即 Func<PetOwner, IEnumerable<String>> //參數3: Func<TSource, TCollection, TResult> resultSelector --> 即 Func<PetOwner, String, IEnumerable<String>> //source: PetOwner[] 陣列 (共 4 個元素) //collectionSelector: petOwner => petOwner.Pets //resultSelector: (petOwner, petName) => new { petOwner, petName } public static IEnumerable<TResult> MySelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector) { // if (source == null) throw Error.ArgumentNull("source"); // if (collectionSelector == null) throw Error.ArgumentNull("collectionSelector"); // if (resultSelector == null) throw Error.ArgumentNull("resultSelector"); return MySelectManyIterator<TSource, TCollection, TResult>(source, collectionSelector, resultSelector); } static IEnumerable<TResult> MySelectManyIterator<TSource, TCollection, TResult>(IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector) { //source: PetOwner[] 陣列 (共 4 個元素) //element: PetOwner[] 陣列裡的每個元素, 即 PetOwner 的物件實體 //collectionSelector: petOwner => petOwner.Pets //resultSelector: (petOwner, petName) => new { petOwner, petName } foreach (TSource element in source) { //把 element 傳入 collectionSelector 執行 //petOwner => petOwner.Pets ---> element => element.Pets foreach (TCollection subElement in collectionSelector(element)) { //把 element, subElement 傳入 resultSelector 執行 //(petOwner, petName) => new { petOwner, petName } ---> (element, subElement) => new { element, subElement } yield return resultSelector(element, subElement); } } } #endregion
步驟2
建立 PetOwner 類別class PetOwner { public string Name { get; set; } public List<String> Pets { get; set; } }
步驟3
在 Program.cs, 加入 3 個 static methods, 並修改 Main() 方法, 以呼叫那 3 個 methods(1) SelectManyEx1()
public static void SelectManyEx1() { PetOwner[] petOwners = { new PetOwner { Name="Higa, Sidney", Pets = new List<string>{ "Scruffy", "Sam" } }, new PetOwner { Name="Ashkenazi, Ronen", Pets = new List<string>{ "Walker", "Sugar" } }, new PetOwner { Name="Price, Vernette", Pets = new List<string>{ "Scratches", "Diesel" } } }; // Query using SelectMany(). IEnumerable<string> query1 = petOwners.MySelectMany(petOwner => petOwner.Pets); // 呼叫自行由 .NET Framework 複製過來的擴充方法 //IEnumerable<string> query1 = petOwners.SelectMany(petOwner => petOwner.Pets); // 呼叫 .NET Framework 的擴充方法 Console.WriteLine("Using SelectMany():"); // Only one foreach loop is required to iterate // through the results since it is a // one-dimensional collection. // 只需要 1 個 foreach 迴圈, 就可以選取第2層 Pets 的元素資料 foreach (string pet in query1) { Console.WriteLine(pet); } // This code shows how to use Select() // instead of SelectMany(). IEnumerable<List<String>> query2 = petOwners.Select(petOwner => petOwner.Pets); Console.WriteLine("\nUsing Select():"); // Notice that two foreach loops are required to // iterate through the results // because the query returns a collection of arrays. // 需要 2 個 foreach 迴圈 foreach (List<String> petList in query2) { foreach (string pet in petList) { Console.WriteLine(pet); } Console.WriteLine(); } //輸出: //Using SelectMany(): //Scruffy //Sam //Walker //Sugar //Scratches //Diesel // //Using Select(): //Scruffy //Sam // //Walker //Sugar // //Scratches //Diesel }
(2) SelectManyEx2()
public static void SelectManyEx2() { PetOwner[] petOwners = { new PetOwner { Name="Higa, Sidney", Pets = new List<string>{ "Scruffy", "Sam" } }, new PetOwner { Name="Ashkenazi, Ronen", Pets = new List<string>{ "Walker", "Sugar" } }, new PetOwner { Name="Price, Vernette", Pets = new List<string>{ "Scratches", "Diesel" } }, new PetOwner { Name="Hines, Patrick", Pets = new List<string>{ "Dusty" } } }; // Project the items in the array by appending the index // of each PetOwner to each pet's name in that petOwner's // array of pets. // // IEnumerable<string> query = // petOwners.SelectMany((petOwner, index) => // petOwner.Pets.Select(pet => index + pet)); // IEnumerable<string> query = petOwners.MySelectMany((petOwner, index) => petOwner.Pets.Select(pet => index + pet)); foreach (string pet in query) { Console.WriteLine(pet); } Console.WriteLine(); //輸出: // // 0Scruffy // 0Sam // 1Walker // 1Sugar // 2Scratches // 2Diesel // 3Dusty }
(3) SelectManyEx3()
public static void SelectManyEx3() { PetOwner[] petOwners = { new PetOwner { Name="Higa", Pets = new List<string>{ "Scruffy", "Sam" } }, new PetOwner { Name="Ashkenazi", Pets = new List<string>{ "Walker", "Sugar" } }, new PetOwner { Name="Price", Pets = new List<string>{ "Scratches", "Diesel" } }, new PetOwner { Name="Hines", Pets = new List<string>{ "Dusty" } } }; // Project the pet owner's name and the pet's name. // //var query = // petOwners // .SelectMany(petOwner => petOwner.Pets, (petOwner, petName) => new { petOwner, petName }) // .Where(ownerAndPet => ownerAndPet.petName.StartsWith("S")) // .Select(ownerAndPet => // new // { // Owner = ownerAndPet.petOwner.Name, // Pet = ownerAndPet.petName // } // ); // var query = petOwners .MySelectMany(petOwner => petOwner.Pets, (petOwner, petName) => new { petOwner, petName } ) .Select(ownerAndPet => new { Owner = ownerAndPet.petOwner.Name, Pet = ownerAndPet.petName } ); // //var query = // petOwners // .MySelectMany ( petOwner => petOwner.Pets, // (petOwner, petName) => new { petOwner, petName } // ) ; // // Print the results. foreach (var obj in query) { Console.WriteLine(obj); } //本例輸出: (without Where() condition) // {Owner=Higa, Pet=Scruffy} // {Owner=Higa, Pet=Sam} // {Owner=Ashkenazi, Pet=Walker} // {Owner=Ashkenazi, Pet=Sugar} // {Owner=Price, Pet=Scratches} // {Owner=Price, Pet=Diesel} // {Owner=Hines, Pet=Dusty} //MSDN範例輸出: (with Where() condition) // {Owner=Higa, Pet=Scruffy} // {Owner=Higa, Pet=Sam} // {Owner=Ashkenazi, Pet=Sugar} // {Owner=Price, Pet=Scratches} }
(4) Main()
static void Main(string[] args) { // CASE 1 Console.WriteLine("====== CASE 1 ======"); SelectManyEx1(); // CASE 2 Console.WriteLine("====== CASE 2 ======"); SelectManyEx2(); // CASE 3 Console.WriteLine("====== CASE 3 ======"); SelectManyEx3(); Console.ReadLine(); }
總結
要瞭解 SelectMany() 這個置於 Enumerable.cs 的擴充方法, 必須要有泛型及委派的基礎, 不然, 還真的不容易看懂.筆者學藝不精, 不確定 SelectMany() 在實務上用到的機會是否很大, 但可作為對泛型及委派觀念再次釐清之用.
MSDN 的這個範例, 可以應用在 Master Detail 結構的資料存取上, 例如: OrderMaster, OrderDetail,
參考文件
.
沒有留言:
張貼留言