用慣 LINQ 後不太能忍受回頭用 foreach 處理集合物件,List<T>、IEnumerable<T> 及物件陣列可直接 Where()、Select() ,基本上涵蓋大部分應用情境,但有些時候還是會遇到一些不支援 LINQ 擴充方法的集合物件。這篇筆記將介紹透過簡單轉換讓集合支援 LINQ 的小技巧。(別像我以前傻傻先 var list = new List<T> 再 foreach 跑 list.Add(…) )
舉兩個我遇過的例子。
有字串 A12+A34+B99-C56+A87,打算用 Regex.Matches 挑出 A 起首的數字代碼,用 Select().ToArray() 轉成字串陣列 "12","34","87"。
有 app.config 如下,我想透過 System.Configuration.ConfigurationManager.AppSettings 將 d:SvcIp d:SvcPort 設定用 ToDictionary() 轉成 Dictionary<string, string>。
<?xmlversion="1.0"encoding="utf-8" ?>
<configuration>
<appSettings>
<addkey="d:SvcIp"value="192.168.1.87"/>
<addkey="d:SvcPort"value="80"/>
<addkey="Interval"value="5000"/>
</appSettings>
</configuration>
依直覺寫出以下程式碼,但無法編譯,理由是 MatchCollection 跟 NameValueCollection 兩種集合型別不適用 LINQ 擴充方法。
![](http://www.darkthread.net/photos/4168-847a-o.gif)
.Select()是 IEnumerable<TSource> 的擴充方法,而 MatchCollection 只實作了 ICollection 與 IEnumerable 介面,故無法直接使用 Select、Where、ToList 等 LINQ 擴充方法:
![](http://www.darkthread.net/photos/4167-69de-o.gif)
.Cast<T>()是 IEnumerable 的擴充方法,可將 IEnumerable 轉換成 IEnumerable<TResult>,如此即可大方使用 LINQ 方法加工。
NameValueCollection 是另一種案例,它繼承自 NameObjectCollectionBase,NameObjectCollectionBase 雖然實作了 IEnumerable,但其 GetEnumerator() 傳回 IEnumerator 只能取回 Name 字串,充其量 Cast<string> 跟 AllKeys 相比多此一舉,故我們改對 AllKeys(型別為 string[],陣列型別可使用 LINQ 擴充方法)進行 Select 產生 Key Value 物件,再依原本構想進行 Where() 篩選與 ToDictionary() 轉換。
![](http://www.darkthread.net/photos/4169-4876-o.gif)
補充一點,appSettings 不接受同一 key 值設定多次,遇重複 key 時以最後一次 value 為準,故 appSettings 設定永遠是一對一。但 NameValueCollections 本身允許同一 key 值對應多組 value,透過 Get("key")取得 "value1,value2,value3" 以逗號分隔的字串,GetValues("key")則傳回 "value1", "value2", "value3" 字串陣列,如要轉換成 Dictionary<string, string>需考量此一狀況。
最後再來個練習,DataTable.Rows 的型別為 DataRowCollection,其父類別 InternalDataCollectionBase 實作了 ICollection, IEnumerable,跟 MatchCollection 狀況相同,解法也一樣,Cast<DataRow> 後可使用 Select(),若想套用我很愛的 ForEach() 方法,則用 ToList() 轉型成 List<DataRow> 即可如願~ 〔註: ForEach() 非 IEnumerable<T> 的擴充方法,為 List<T> 的專屬方法〕
![](http://www.darkthread.net/photos/4170-d679-o.gif)