🐛 IEnumerable IEnumerable 是所有集合的基礎款,ICollection 繼承 IEnumerable,IList 繼承 ICollection,而我們常用的 List 就是 IList 的實作。 所以 IEnumerable 相當於阿嬤,要敬老尊賢,或者也可以想像你訂閱了微軟的集合使用基礎方案 : IEnumerable。
基於這個基礎方案我們獲得這些功能:
可以用 foreach 逐筆處理(走訪抽象)。 1 2 3 IEnumerable<string > names = new [] { "Ann" , "Bob" , "Cindy" }; foreach (var n in names) Console.WriteLine(n);
支援延遲執行(LINQ-to-Objects 的 Where/Select/Take) 1 2 3 4 5 var data = Enumerable.Range(1 , 1 _000_000); var firstFiveEvens = data.Where(x => x % 2 == 0 ) .Take(5 ) .ToList();
用 yield return 自訂走訪 1 2 3 4 5 6 7 8 9 10 11 12 13 public static IEnumerable<int > EvenNumbers () { int n = 0 ; while (true ) { yield return n; n += 2 ; } } foreach (var x in EvenNumbers ().Take (5 )) Console.WriteLine (x ) ;
Count() / Contains() / Any()… 是 Enumerable 的延伸方法。 1 2 IEnumerable<int > seq = Enumerable.Range(1 , 10 ); int c = seq.Count();
IEnumerable 的限制 只讀、前進式(無索引、不可改)。 每次枚舉都要重新執行,若來源昂貴會付出重複成本。
✅ 解法:先 .ToList() 快取。
1 2 3 4 5 6 7 8 9 IEnumerable<string > FromApi () => CallSlowApiAsEnumerable();var first = FromApi().First(); var count = FromApi().Count(); var cache = FromApi().ToList();var first2 = cache.First();var count2 = cache.Count;
🐛 ICollection 升級訂閱方案後就可以使用 ICollection! ICollection(在 IEnumerable 之上):能被修改,且有 Count 屬性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public int CountSpecialCharacters (ICollection<char > specialChars ){ if (!specialChars.IsReadOnly) { specialChars.Add('~' ); specialChars.Add('!' ); } return specialChars.Count; } var s1 = new List<char > { '$' , '%' , '^' };var s2 = new HashSet<char > { '$' , '%' , '^' };Console.WriteLine(CountSpecialCharacters(s1)); Console.WriteLine(CountSpecialCharacters(s2));
MatchCollection Regex.Matches(…) 會回傳 MatchCollection,可以:
foreach 枚舉
使用 LINQ 運算子
1 2 3 4 5 6 7 8 var text = "A12+A34+B99-C56+A87" ;var matches = Regex.Matches(text, "A(?<n>\\d+)" );foreach (Match m in matches) Console.WriteLine($"完整: {m.Value} , 數字: {m.Groups["n" ].Value} " ); var numbers = matches.Select(m => int .Parse(m.Groups["n" ].Value)).ToList();
NameValueCollection:為什麼不能直接 Where? 因為 NameValueCollection 沒有實作 IEnumerable,只能轉成 AllKeys 來 LINQ。
1 2 3 4 5 var setting = ConfigurationManager.AppSettings .AllKeys .Select(o => new { Key = o, Value = ConfigurationManager.AppSettings[o] }) .Where(o => o.Key.StartsWith("J" )) .ToDictionary(k => k.Key, v => v.Value);
另一個方法是用 Cast()
1 2 3 var app = ConfigurationManager.AppSettings;var pairs = app.AllKeys.Cast<string >() .Select(k => KeyValuePair.Create(k, app[k]));
🐛 IList IList 在 ICollection 之上,新增了 索引操作 (Insert/RemoveAt)。
1 2 3 4 5 6 7 8 9 10 public int CountSpecialCharacters (IList<char > specialCharacters ){ specialCharacters.Add('~' ); specialCharacters.Add('!' ); specialCharacters.Insert(0 , '$' ); return specialCharacters.IndexOf('~' ); } IList<char > charList = new List<char >(){'$' , '%' , '^' }; CountSpecialCharacters(charList).Dump();
典型應用:
待辦清單 App(新增/刪除第 n 項) 購物車(修改數量、刪除第 n 個商品) 播放清單(移動歌曲)
🐛 使用甚麼集合
方法回傳值 1 2 3 4 5 6 7 8 public List<Order> GetOrders () => _context.Orders.ToList(); public IEnumerable<Order> GetOrders () => _context.Orders.ToList(); public IReadOnlyList<Order> GetOrders () => _context.Orders.ToList();
查詢 → IEnumerable 或 IReadOnlyCollection
索引 → IReadOnlyList
真的要修改才回傳 List
方法參數 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void ProcessOrders (IEnumerable<Order> orders ) { foreach (var o in orders) Console.WriteLine(o.Id); } public void NormalizeTags (ICollection<string > tags ) { tags.Add("Default" ); tags.Remove("Deprecated" ); } public void Swap <T >(IList<T> list, int i, int j ) { (list[i], list[j]) = (list[j], list[i]); }
類別成員 1 2 private List<string > _tags = new (); public IReadOnlyCollection<string > Tags => _tags;
LINQ 與延遲執行 1 2 3 public List<User> GetUsers () => _context.Users.ToList(); public IQueryable<User> QueryUsers () => _context.Users;