再看一次昨天的程式碼的片段
GetMemberByNameAsync(name)
.TaskBind(member => member
.Map(m => new Attendance{ name = m.Name })
.Map(CreateAttendanceAsync))
這段程式碼看起來有礙觀瞻,在TaskBind裡面塞了一大坨東西可讀性說不上很好,那有沒有辦法把裡面的Map拉到外面來跟TaskBind對齊,可能長的像這樣呢?
GetMemberByNameAsync(name)
.TaskBind(...)
.Map(...)
先離個題,我們來看看Linq裡面的SelectMany
通常我們使用SelectMany是在攤平兩層集合時,大概是下面的作法
public static IEnumerable<TResult> SelectMany<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TResult>> selector)
{
foreach (var item in source)
{
foreach (var subItem in selector(item))
{
yield return subItem;
}
}
}
首先遍歷最外層的集合,將我們需要的子集合挑出(selector做的事情),然後再遍歷子集合一次,用yield return把子集合裡面的元素一一傳出來,這邊要注意foreach是一個把物件從IEnumerable這個盒子中拿出來的方法,明天再次講到Task物件時會用到類似的概念。
SelectMany還有另外一個多載
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TCollection>> collectionSelector,
Func<TSource, TCollection, TResult> resultSelector)
{
foreach (var item in source)
{
foreach (var subItem in collectionSelector(item))
{
yield return resultSelector(item,subItem);
}
}
}
除了單純的拋出元素,其實我也可以對要拋出來的元素做加工(resultSelector),而這個功能有什麼用呢?
var list1 = new List<string> { "洛伊德", "約兒", "安妮亞" };
var list2 = new List<string> { "佛傑" };
list1
.SelectMany(
x => list2,
(a, b) => $"{a}.{b}")
.ToList()
.ForEach(Console.WriteLine);
// 洛伊德.佛傑
// 約兒.佛傑
// 安妮亞.佛傑
//Query expression
from a in list1
from b in list2
select $"{a}.{b}"
在這個多載中可以看到兩個集合被組合在一起,如果用查詢運算式的話是不是就達到了把SelectMany裡面的東西提出來拉平的效果呢?
今天介紹了可以透過SelectMany的多載來達到拉平的效果,基於函數可以作為委派傳遞,實物上list1與list2的內容也可以放待執行的函數,而且SelectMany是可以無限串連的!只要把Map/Bind寫成Select/SelectMany,欺騙C#,任何的單子都可以用查詢運算式組合。明天就來幫Task還有Nullable重寫SelectMany看看效果吧!當初看到這個真的覺得是什麼巫術,實在太神奇了!!!!!