最近經過大師的指導後,對於 Yield Return 又或是迭代器有更深的感受,先來看一段以前常常會寫的程式
1 2 3 4 5 6 7 8 9 10 11 12 13 void Main ( ) { var users = GetUser(idList); var notInBlackList = users.Where(x=> !x.InBlackList); } public List<User> GetUser (List<int > ids ) { }
如果 Id 一次有一百萬筆,為了避免擊沉 Database 做了分批查詢也是很合理的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void Main ( ) { var users = GetUser(idList); var notInBlackList = users.Where(x=> !x.InBlackList); } public List<User> GetUser (List<int > ids ) { var Result = new List<User>(); var batchCount = 1000 ; for (int i = 0 ; i < 搜尋次數; i++) { Result.AddRange( UserRepository.Get(ids.Skip(i * batchCount).Take(batchCount))); } }
當然也可以從呼叫端分批處理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void Main ( ) { var batchCount = 1000 ; for (int i = 0 ; i < 搜尋次數; i++) { var users = GetUser(idList); var notInBlackList = users.Where(x=> !x.InBlackList); } } public List<User> GetUser (List<int > ids ) { }
如果需要再更複雜一些,要依據使用者的設定濾掉一些人,最終要將處理過程都填回 DB ,以便追蹤那些人是因為那些條件被過濾掉的 (不然被客戶抱怨沒收到通知,不知道往哪找),這時候程式就會變成這樣….
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void Main ( ) { var batchCount = 1000 ; var temp = new List<User>() for (int i = 0 ; i < 搜尋次數; i++) { var users = GetUser(idList); var notInBlackList = users.Where(x=> !x.InBlackList); var wantToReceiveThisMessage = notInBlackList.Where(x => x.BlockMessageType != ThisMessageType); temp.Add(InBlackList); temp.Add(DontWantToReceiveThisMessage); if (temp.Count > 5000 ) { LogRepository.Add(temp); temp.Clear(); } } } public List<User> GetUser (List<int > ids ) { }
條件可以無限增長上去,程式也會變得越來越複雜,當然這時候可以採取一些物件導向的設計將功能職責拆開,不過這不是這篇的重點所以跳過。
更符合語意的寫法 如果有用過 FluentAssertion 、Nsubstitute 等套件,應該會發現它 API 設計得非常好讀好懂
1 2 3 customer.Active.Should().BeTrue(because, becauseArgs); theObject.Should().NotBeSameAs(otherObject);
讓程式不再是一段一段的,而是一看程式就能理解這邊在做什麼,而且也因為如此讓每個方法的職責更清楚單一,所以我們可以將上述的程式改成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 void Main ( ) { Ids.GetUser().NotInBlackList().WantToReceiveThisMessage(ThisMessageType).Record(); } public static IEnumerable<User> GetUser (this IEnumerable<int > ids ) { var temp = new List<int >(); foreach (var id in ids) { temp.Add(id); if (temp.Count == 1000 ) { foreach (var user in UserRepository.Get(temp)) { yield return user; } temp.Clear(); } } if (temp.Count > 0 ) { foreach (var user in UserRepository.Get(temp)) { yield return user; } } yield break ; } public static IEnumerable<User> NotInBlackList (this IEnumerable<User> users ) { foreach (var user in users) { if (user.InBlackList == false ) { yield return user; } } yield break ; } public static IEnumerable<User> NotInBlackList (this IEnumerable<User> users, MessageType type ) { foreach (var user in users) { if (user.BlockMessageType != type) { yield return user; } } yield break ; } public static IEnumerable<User> Record (this IEnumerable<User> users ) { var temp = new List<User>(); foreach (var user in users) { temp.Add(user); if (temp.Count == 5000 ) { LogRepository.Add(temp); temp.Clear(); } yield return user; } if (temp.Count > 5000 ) { LogRepository.Add(temp); } yield break ; }
如果還需要紀錄每個過程的分別過濾的狀態,只需要在方法開個統計的物件參數,在每筆過程中做紀錄即可,不只讓整個程式可讀性更高、內聚更強,之後如果不想要紀錄或不想要過濾黑名單,也可以很快的調整完成
1 Ids.GetUser().WantToReceiveThisMessage(ThisMessageType);