SelectMany 的功用與 Select 相同 , 都是依照我們設定的委派去對資料源做出處理 , 並輸出. 但是差異是 Select 只取得第一層的查詢結果放到輸出序列中 , 但是 SelectMany 若發現查詢結果回傳的是一個可以列舉的序列 , 則會再進一步把這個序列中的項目取出來 , 放到輸出序列中 , 也就是用 SelectMany 運算子 , 查詢出來的輸出序列之深度會比 Select 少一層. SelectMany 通常是在處理巢狀集合的時候使用 , 可以替我們省去多層迴圈 .
一言以蔽之 , SelectMany 可以將集合中每個元素內的子集合合併為一個新的集合. 換句話說 , SelectMany 可以幫我們把子集合展開成一個.
使用時機
- 需要取出集合元素中的每一個子集合中的每一個元素. e.g. 學生物件有一個List<朋友> . 我們想要取出List<學生> 中所有的朋友物件.
- 需要對集合元素中的每一個子集合中的每一個元素做投影的動作.
你喜歡跑雙層迴圈取值 , 那這個函數可以當作沒看到
多載形式
-
SelectMany<TSource,TResult>(this IEnumerable<TSource>, Func<TSource,IEnumerable<TResult>>)
- 將序列的每個項目都投影成 IEnumerable<T>,並將產生的序列簡化成單一序列。
-
SelectMany<TSource,TResult>(this IEnumerable<TSource>, Func<TSource,Int32,IEnumerable<TResult>>)
- 將序列的每個項目都投影成 IEnumerable<T>,並將產生的序列簡化成單一序列。 各來源項目的索引是在該項目的投影表單中使用。
-
SelectMany<TSource,TCollection,TResult>(this IEnumerable<TSource>, Func<TSource,IEnumerable<TCollection>>, Func<TSource,TCollection,TResult>)
- 將序列的每個項目投影為 IEnumerable<T>、將產生的序列簡化成單一序列,並對其中的每個項目叫用結果選取器函式。
-
SelectMany<TSource,TCollection,TResult>(this IEnumerable<TSource>, Func<TSource,Int32,IEnumerable<TCollection>>, Func<TSource,TCollection,TResult>)
- 將序列的每個項目投影為 IEnumerable<T>、將產生的序列簡化成單一序列,並對其中的每個項目叫用結果選取器函式。 各來源項目的索引是在該項目的中繼投影表單中使用。
解釋
- 3 & 4 與 1 & 2 相比
- 多了一個 resultSelector 可以使用 , 它可以傳入TSource (原本集合的每一個元素)及 TCollection (子集合中的每一個元素) , 讓我們可以讓外層的資料跟內層的資料合為同一筆資料. 當然 , 因為有 TCollection 可以使用 , 自然也能再對 TCollection 做投影的處理.
- 使用 1 以及 2 將需要展開的集合平展開成一個集合
- index 是 source 的索引.
- Func 回傳 IEnumerable 型別. 這代表你需要指定一個能走訪的集合回傳.
範例
有三個學生{小昂,小王,大名}
- 小昂有一個朋友 , 兩個手機.
- 小王有一個朋友 , 一個手機.
- 大名有兩個朋友 , 一個手機.
試不使用雙層迴圈來取出他們的資訊並列出
- 僅取出號碼.
- 僅取出號碼 , 但同個人的號碼 , 需要用相同的編號.
- 僅取出朋友姓名 , 但必須標註這是哪個學生的朋友.
- 取出朋友的資訊 , 但必須與學生的資訊配對再一起. (湊數 , 我想不到好例子= =)
結構
public class Student
{
public string Name { get; set; }
public List<Friend> Friends { get; set; }
public List<string> Phones { get; set; }
}
public class Friend
{
public string Name { get; set; }
}
public static List<Student> GetStudents()
{
return new List<Student>
{
new Student{Name = "小昂", Friends=new List<Friend>{ new Friend {Name="V" } } ,Phones=new List<string>{ "0989782268","0920755420" }},
new Student{Name = "小王", Friends=new List<Friend>{ new Friend {Name="Q" } } ,Phones=new List<string>{ "1234567888" }},
new Student{Name = "大名", Friends=new List<Friend>{ new Friend {Name="T" }, new Friend { Name = "S" } } ,Phones=new List<string>{ "6666666666" }},
};
}
測試程式
static void Main(string[] args)
{
var students = GetStudents();
var queryPhones = students.SelectMany((student) => student.Phones);
foreach (var phone in queryPhones)
{
Console.WriteLine(phone);
}
Console.WriteLine();
var queryPhonesWithIndex = students.SelectMany((student, index) => student.Phones.Select(phone => $"編號 {index}. 的 {phone}")); ;
foreach (var phone in queryPhonesWithIndex)
{
Console.WriteLine(phone);
}
Console.WriteLine();
var queryFriends = students.SelectMany(student => student.Friends, (student, friend) => $"{student.Name} 有朋友叫做 {friend.Name}");
foreach (var item in queryFriends)
{
Console.WriteLine(item);
}
Console.WriteLine();
var queryFriendsWithIndex = students.SelectMany((student, index) => student.Friends.Select(friend => new { ID = index, friend.Name }), (student, friend) => new { student, friend });
foreach (var item in queryFriendsWithIndex)
{
Console.WriteLine($"編號{item.friend.ID}號 的{item.student.Name} 有朋友叫做 {item.friend.Name}");
}
Console.ReadKey();
}
結果
簡單實作自己的SelectMany
作法
- MySelectMany : 負責對參數做出檢查或是判斷
- MySelectManyIterator : 負責回傳 IEnumerable
- 其實就是走訪指定的 Source
- resultSelector : 將 subItem 轉換成結果.
public static IEnumerable<TResult> MySelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TResult>> selector)
{
if (source is null || selector is null)
throw new Exception("Exception");
return MySelectManyIterator(source, selector);
}
public static IEnumerable<TResult> MySelectManyIterator<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TResult>> selector)
{
int index = -1;
foreach (var item in source)
{
checked
{
index++;
}
foreach (var subItem in selector(item, index))
{
yield return subItem;
}
}
}
public static IEnumerable<TResult> MySelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)
{
if (source is null || collectionSelector is null || resultSelector is null)
throw new Exception("Exception");
return MySelectManyIterator(source, collectionSelector, resultSelector);
}
private static IEnumerable<TResult> MySelectManyIterator<TSource, TCollection, TResult>(IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)
{
int index = -1;
foreach (var item in source)
{
checked
{
index++;
}
foreach (var subItem in collectionSelector(item, index))
{
yield return resultSelector(item, subItem);
}
}
}
參考 Source Code
Thank you!
You can find me on
若有謬誤 , 煩請告知 , 新手發帖請多包涵